Skip to content

Commit

Permalink
feat(docs): better docs with gpt-4o
Browse files Browse the repository at this point in the history
  • Loading branch information
BobTheBuidler committed Nov 13, 2024
1 parent f05f864 commit ff33028
Show file tree
Hide file tree
Showing 37 changed files with 1,754 additions and 522 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@

`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.

It also contains implementations of various asyncio primitives with extra functionality, including queues and various types of locks.
\# TODO add links to various objects' docs

## Installation

`ez-a-sync` can be installed via pip:
Expand Down
25 changes: 24 additions & 1 deletion a_sync/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,26 @@
"""
This module initializes the a_sync library by importing and organizing various components, utilities, and classes.
It provides a convenient and unified interface for asynchronous programming with a focus on flexibility and efficiency.
The `a_sync` library offers decorators and base classes to facilitate writing both synchronous and asynchronous code.
It includes the `@a_sync()` decorator and the `ASyncGenericBase` class, which allow for creating functions and classes
that can operate in both synchronous and asynchronous contexts. Additionally, it provides enhanced asyncio primitives,
such as queues and locks, with extra functionality.
Modules and components included:
- `aliases`, `exceptions`, `iter`, `task`: Core modules of the library.
- `ASyncGenericBase`, `ASyncGenericSingleton`, `a_sync`: Base classes and decorators for dual-context execution.
- `apply_semaphore`: Function to apply semaphores to coroutines.
- `ASyncCachedPropertyDescriptor`, `ASyncPropertyDescriptor`, `cached_property`, `property`: Property descriptors for async properties.
- `as_completed`, `create_task`, `gather`: Enhanced asyncio functions.
- Executors: `AsyncThreadPoolExecutor`, `AsyncProcessPoolExecutor`, `PruningThreadPoolExecutor` for async execution.
- Iterators: `ASyncFilter`, `ASyncSorter`, `ASyncIterable`, `ASyncIterator` for async iteration.
- Utilities: `all`, `any`, `as_yielded` for async utilities.
Alias for backward compatibility:
- `ASyncBase` is an alias for `ASyncGenericBase`, which will be removed eventually, probably in version 0.1.0.
"""

from a_sync import aliases, exceptions, iter, task
from a_sync.a_sync import ASyncGenericBase, ASyncGenericSingleton, a_sync
from a_sync.a_sync.modifiers.semaphores import apply_semaphore
Expand Down Expand Up @@ -76,4 +99,4 @@
# executor aliases
"ThreadPoolExecutor",
"ProcessPoolExecutor",
]
]
89 changes: 82 additions & 7 deletions a_sync/_smart.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
"""
This module defines smart future and task utilities for the a_sync library.
These utilities provide enhanced functionality for managing asynchronous tasks and futures,
including task shielding and a custom task factory for creating SmartTask instances.
"""

import asyncio
import logging
import warnings
Expand All @@ -18,11 +24,20 @@


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.
"""

_queue: Optional["SmartProcessingQueue[Any, Any, T]"] = None
_key: _Key
_waiters: "weakref.WeakSet[SmartTask[T]]"

def __await__(self: Union["SmartFuture", "SmartTask"]) -> Generator[Any, None, T]:
"""
Await the smart future or task, handling waiters and logging.
"""
if self.done():
return self.result() # May raise too.
self._asyncio_future_blocking = True
Expand All @@ -37,6 +52,9 @@ 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.
"""
if self.done():
# if there are any waiters left, there won't be once the event loop runs once
return 0
Expand All @@ -45,17 +63,31 @@ def num_waiters(self: Union["SmartFuture", "SmartTask"]) -> int:
def _waiter_done_cleanup_callback(
self: Union["SmartFuture", "SmartTask"], waiter: "SmartTask"
) -> None:
"Removes the waiter from _waiters, and _queue._futs if applicable"
"""
Callback to clean up waiters when a waiter task is done.
Removes the waiter from _waiters, and _queue._futs if applicable
"""
if not self.done():
self._waiters.remove(waiter)

def _self_done_cleanup_callback(self: Union["SmartFuture", "SmartTask"]) -> None:
"""
Callback to clean up waiters and remove the future from the queue when done.
"""
self._waiters.clear()
if queue := self._queue:
queue._futs.pop(self._key)


class SmartFuture(_SmartFutureMixin[T], asyncio.Future):
"""
A smart future that tracks waiters and integrates with a smart processing queue.
Inherits from both _SmartFutureMixin and asyncio.Future, providing additional functionality
for tracking waiters and integrating with a smart processing queue.
"""

_queue = None
_key = None

Expand All @@ -66,6 +98,14 @@ def __init__(
key: Optional[_Key] = None,
loop: Optional[asyncio.AbstractEventLoop] = None,
) -> None:
"""
Initialize the SmartFuture with an optional queue and key.
Args:
queue: Optional; a smart processing queue.
key: Optional; a key identifying the future.
loop: Optional; the event loop.
"""
super().__init__(loop=loop)
if queue:
self._queue = weakref.proxy(queue)
Expand All @@ -78,11 +118,16 @@ def __repr__(self):
return f"<{type(self).__name__} key={self._key} waiters={self.num_waiters} {self._state}>"

def __lt__(self, other: "SmartFuture[T]") -> bool:
"""heap considers lower values as higher priority so a future with more waiters will be 'less than' a future with less waiters."""
# other = other_ref()
# if other is None:
# # garbage collected refs should always process first so they can be popped from the queue
# return False
"""
Compare the number of waiters to determine priority in a heap.
Lower values indicate higher priority, so more waiters means 'less than'.
Args:
other: Another SmartFuture to compare with.
Returns:
True if self has more waiters than other.
"""
return self.num_waiters > other.num_waiters


Expand All @@ -92,17 +137,43 @@ def create_future(
key: Optional[_Key] = None,
loop: Optional[asyncio.AbstractEventLoop] = None,
) -> SmartFuture[V]:
"""
Create a SmartFuture instance.
Args:
queue: Optional; a smart processing queue.
key: Optional; a key identifying the future.
loop: Optional; the event loop.
Returns:
A SmartFuture instance.
"""
return SmartFuture(queue=queue, key=key, loop=loop or asyncio.get_event_loop())


class SmartTask(_SmartFutureMixin[T], asyncio.Task):
"""
A smart task that tracks waiters and integrates with a smart processing queue.
Inherits from both _SmartFutureMixin and asyncio.Task, providing additional functionality
for tracking waiters and integrating with a smart processing queue.
"""

def __init__(
self,
coro: Awaitable[T],
*,
loop: Optional[asyncio.AbstractEventLoop] = None,
name: Optional[str] = None,
) -> None:
"""
Initialize the SmartTask with a coroutine and optional event loop.
Args:
coro: The coroutine to run in the task.
loop: Optional; the event loop.
name: Optional; the name of the task.
"""
super().__init__(coro, loop=loop, name=name)
self._waiters: Set["asyncio.Task[T]"] = set()
self.add_done_callback(SmartTask._self_done_cleanup_callback)
Expand Down Expand Up @@ -166,6 +237,10 @@ def shield(
res = await shield(something())
except CancelledError:
res = None
Args:
arg: The awaitable to shield from cancellation.
loop: Optional; the event loop. Deprecated since Python 3.8.
"""
if loop is not None:
warnings.warn(
Expand Down Expand Up @@ -216,4 +291,4 @@ def _outer_done_callback(outer):
"SmartTask",
"smart_task_factory",
"set_smart_task_factory",
]
]
19 changes: 17 additions & 2 deletions a_sync/a_sync/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
"""
This module enables developers to write both synchronous and asynchronous code without having to write redundant code.
The two main objects you should use are
- a decorator `@a_sync()`
- a base class `ASyncGenericBase` which can be used to create classes that can be utilized in both synchronous and asynchronous contexts.
The rest of the objects are exposed for type checking only, you should not make use of them otherwise.
"""

# TODO: double check on these before adding them to docs
#- two decorators @:class:`property` and @:class:`cached_property` for the creation of dual-function properties and cached properties, respectively.

from a_sync.a_sync.base import ASyncGenericBase
from a_sync.a_sync.decorator import a_sync
from a_sync.a_sync.function import (
Expand Down Expand Up @@ -27,10 +40,12 @@
# entrypoints
"a_sync",
"ASyncGenericBase",
# classes
"ASyncFunction",
# maybe entrypoints (?)
# TODO: double check how I intended for these to be used
"property",
"cached_property",
# classes exposed for type hinting only
"ASyncFunction",
"ASyncPropertyDescriptor",
"ASyncCachedPropertyDescriptor",
"HiddenMethod",
Expand Down
11 changes: 7 additions & 4 deletions a_sync/a_sync/_descriptor.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,21 @@

from a_sync._typing import *
from a_sync.a_sync import decorator
from a_sync.a_sync.function import ASyncFunction, ModifiedMixin, ModifierManager
from a_sync.a_sync.function import (ASyncFunction, ModifierManager,
_ModifiedMixin)

if TYPE_CHECKING:
from a_sync import TaskMapping


class ASyncDescriptor(ModifiedMixin, Generic[I, P, T]):
class ASyncDescriptor(_ModifiedMixin, Generic[I, P, T]):
"""
A descriptor base class for asynchronous methods and properties.
A descriptor base class for dual-function ASync methods and properties.
This class provides functionality for mapping operations across multiple instances
and includes utility methods for common operations.
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.
"""

__wrapped__: AnyFn[Concatenate[I, P], T]
Expand Down
16 changes: 11 additions & 5 deletions a_sync/a_sync/_flags.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,15 @@
This module provides functionality for handling synchronous and asynchronous flags
in the ez-a-sync library.
ez-a-sync uses 'flags' to indicate whether objects / function calls will be sync or async.
ez-a-sync uses 'flags' to indicate whether objects or function calls will be synchronous or asynchronous.
You can use any of the provided flags, whichever makes most sense for your use case.
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".
NEGATIVE_FLAGS: Set of flags indicating asynchronous behavior. Currently includes "asynchronous".
VIABLE_FLAGS: Set of all valid flags, combining both synchronous and asynchronous indicators.
"""

from typing import Any
Expand Down Expand Up @@ -32,7 +38,7 @@ def negate_if_necessary(flag: str, flag_value: bool) -> bool:
The potentially negated flag value.
Raises:
:class:`exceptions.InvalidFlag`: If the flag is not recognized.
exceptions.InvalidFlag: If the flag is not recognized.
"""
validate_flag_value(flag, flag_value)
if flag in AFFIRMATIVE_FLAGS:
Expand All @@ -54,8 +60,8 @@ def validate_flag_value(flag: str, flag_value: Any) -> bool:
The validated flag value.
Raises:
:class:`exceptions.InvalidFlagValue`: If the flag value is not a boolean.
exceptions.InvalidFlagValue: If the flag value is not a boolean.
"""
if not isinstance(flag_value, bool):
raise exceptions.InvalidFlagValue(flag, flag_value)
return flag_value
return flag_value
11 changes: 4 additions & 7 deletions a_sync/a_sync/_helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,8 @@ def _await(awaitable: Awaitable[T]) -> T:
Args:
awaitable: The awaitable object to be awaited.
Returns:
The result of the awaitable.
Raises:
:class:`exceptions.SyncModeInAsyncContextError`: If the event loop is already running.
exceptions.SyncModeInAsyncContextError: If the event loop is already running.
"""
try:
return a_sync.asyncio.get_event_loop().run_until_complete(awaitable)
Expand All @@ -39,13 +36,13 @@ def _asyncify(func: SyncFn[P, T], executor: Executor) -> CoroFn[P, T]: # type:
Args:
func: The synchronous function to be converted.
executor: The executor to run the synchronous function.
executor: The executor used to run the synchronous function.
Returns:
A coroutine function wrapping the input function.
Raises:
:class:`exceptions.FunctionNotSync`: If the input function is already asynchronous.
exceptions.FunctionNotSync: If the input function is a coroutine function or an instance of ASyncFunction.
"""
from a_sync.a_sync.function import ASyncFunction

Expand All @@ -59,4 +56,4 @@ async def _asyncify_wrap(*args: P.args, **kwargs: P.kwargs) -> T:
loop=a_sync.asyncio.get_event_loop(),
)

return _asyncify_wrap
return _asyncify_wrap
Loading

0 comments on commit ff33028

Please sign in to comment.