diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..e75e147 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,8 @@ +root = true + +[*] +indent_style = space +indent_size = 4 + +[*.{js,ts,yaml,yml,html,json,css}] +indent_size = 2 \ No newline at end of file diff --git a/.readthedocs.yaml b/.readthedocs.yaml index ce5892c..f6f4ced 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -4,16 +4,9 @@ build: os: ubuntu-22.04 tools: python: "3.12" - jobs: - post_create_environment: - - pip install uv - - uv pip compile --extra docs pyproject.toml -o requirements.txt - post_install: - - pip install -e . - -python: - install: - - requirements: requirements.txt - -sphinx: - configuration: src/docs/conf.py \ No newline at end of file + commands: + - asdf plugin add uv + - asdf install uv latest + - asdf global uv latest + - uv sync --group docs + - uv run -m sphinx -T -b html -d docs/_build/doctrees -D language=en src/docs $READTHEDOCS_OUTPUT/html \ No newline at end of file diff --git a/Makefile b/Makefile index 4c464c3..e376d0e 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build +SPHINXBUILD ?= uv run sphinx-build SOURCEDIR = src/docs BUILDDIR = build diff --git a/make.bat b/make.bat deleted file mode 100644 index dcb8c34..0000000 --- a/make.bat +++ /dev/null @@ -1,35 +0,0 @@ -@ECHO OFF - -pushd %~dp0 - -REM Command file for Sphinx documentation - -if "%SPHINXBUILD%" == "" ( - set SPHINXBUILD=sphinx-build -) -set SOURCEDIR=src/docs -set BUILDDIR=build - -%SPHINXBUILD% >NUL 2>NUL -if errorlevel 9009 ( - echo. - echo.The 'sphinx-build' command was not found. Make sure you have Sphinx - echo.installed, then set the SPHINXBUILD environment variable to point - echo.to the full path of the 'sphinx-build' executable. Alternatively you - echo.may add the Sphinx directory to PATH. - echo. - echo.If you don't have Sphinx installed, grab it from - echo.https://www.sphinx-doc.org/ - exit /b 1 -) - -if "%1" == "" goto help - -%SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% -goto end - -:help -%SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% - -:end -popd diff --git a/pyproject.toml b/pyproject.toml index f13cdf7..e97e533 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,6 +33,8 @@ Repository = "https://github.com/phi-friday/async-wrapper" [project.optional-dependencies] uvloop = ["uvloop; platform_system != 'Windows'"] sqlalchemy = ["sqlalchemy[asyncio]", "greenlet"] + +[dependency-groups] test = [ "pytest>=8.0.0", "trio>=0.24.0", @@ -40,29 +42,39 @@ test = [ "aiosqlite>=0.20.0", "pytest-xdist>=3.6.1", ] -docs = [ - "sphinx>=7.1.0", - "readthedocs-sphinx-search>=0.3.2", - "sphinx-rtd-theme>=2.0.0", - "sphinx-mdinclude>=0.5.3", -] - -[tool.uv] -managed = true -dev-dependencies = [ +dev = [ + { include-group = "test"}, "ruff==0.6.8", "ipykernel>=6.29.0", "pre-commit>=3.5.0", - "async_wrapper[uvloop,sqlalchemy,test]", "poethepoet>=0.27.0", + "async_wrapper[uvloop,sqlalchemy]", ] +docs = [ + "linkify-it-py>=2.0.3; python_version < '3.10'", + "myst-parser>=3.0.1; python_version < '3.10'", + "sphinx-autodoc-typehints>=2.3.0; python_version < '3.10'", + "sphinx-immaterial>=0.12.4; python_version < '3.10'", + "sphinx>=7.4.7; python_version < '3.10'", + # 3.10 + "linkify-it-py>=2.0.3; python_version >= '3.10'", + "myst-parser>=4.0.0; python_version >= '3.10'", + "sphinx>=8.1.3; python_version >= '3.10'", + "sphinx-autodoc-typehints>=2.5.0; python_version >= '3.10'", + "sphinx-immaterial>=0.12.4; python_version >= '3.10'", + # + "tomli>=2" +] + +[tool.uv] +managed = true +default-groups = ["dev"] [tool.poe.tasks] -html = "make html" lint = ["_lint:check", "_lint:format"] +check = "pre-commit run --all-files --show-diff-on-failure" "_lint:check" = "ruff check src --fix" "_lint:format" = "ruff format src" -check = "pre-commit run --all-files --show-diff-on-failure" [build-system] requires = ["hatchling", "hatch-vcs"] diff --git a/ruff.toml b/ruff.toml index 0004bd4..82afcdd 100644 --- a/ruff.toml +++ b/ruff.toml @@ -107,7 +107,7 @@ ignore = [ "D102", "PLR2004", ] -"./src/docs/conf.py" = ['E402'] +"src/docs/conf.py" = ["INP001", "A001"] [format] indent-style = "space" diff --git a/src/async_wrapper/convert/_async.py b/src/async_wrapper/convert/_async.py index 0c2b036..882734f 100644 --- a/src/async_wrapper/convert/_async.py +++ b/src/async_wrapper/convert/_async.py @@ -9,31 +9,29 @@ if TYPE_CHECKING: from collections.abc import Awaitable, Callable, Coroutine -ValueT = TypeVar("ValueT", infer_variance=True) -ParamT = ParamSpec("ParamT") +_T = TypeVar("_T", infer_variance=True) +_P = ParamSpec("_P") __all__ = ["sync_to_async"] -class Async(Generic[ParamT, ValueT]): - def __init__(self, func: Callable[ParamT, ValueT]) -> None: +class Async(Generic[_P, _T]): + def __init__(self, func: Callable[_P, _T]) -> None: self._func = func @cached_property - def _wrapped(self) -> Callable[ParamT, Coroutine[Any, Any, ValueT]]: + def _wrapped(self) -> Callable[_P, Coroutine[Any, Any, _T]]: @wraps(self._func) - async def inner(*args: ParamT.args, **kwargs: ParamT.kwargs) -> ValueT: + async def inner(*args: _P.args, **kwargs: _P.kwargs) -> _T: return await to_thread.run_sync(partial(self._func, *args, **kwargs)) return inner - async def __call__(self, *args: ParamT.args, **kwargs: ParamT.kwargs) -> ValueT: + async def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _T: return await self._wrapped(*args, **kwargs) -def sync_to_async( - func: Callable[ParamT, ValueT], -) -> Callable[ParamT, Awaitable[ValueT]]: +def sync_to_async(func: Callable[_P, _T]) -> Callable[_P, Awaitable[_T]]: """ Convert a synchronous function to an asynchronous function. @@ -45,32 +43,34 @@ def sync_to_async( that behaves equivalently to the input synchronous function. Example: - >>> import time - >>> - >>> import anyio - >>> - >>> from async_wrapper import sync_to_async - >>> - >>> - >>> @sync_to_async - >>> def test(x: int) -> int: - >>> print(f"[{x}] test: start") - >>> time.sleep(1) - >>> print(f"[{x}] test: end") - >>> return x - >>> - >>> - >>> async def main() -> None: - >>> start = time.perf_counter() - >>> async with anyio.create_task_group() as task_group: - >>> for i in range(4): - >>> task_group.start_soon(test, i) - >>> end = time.perf_counter() - >>> assert end - start < 1.1 - >>> - >>> - >>> if __name__ == "__main__": - >>> anyio.run(main) + .. code-block:: python + + import time + + import anyio + + from async_wrapper import sync_to_async + + + @sync_to_async + def test(x: int) -> int: + print(f"[{x}] test: start") + time.sleep(1) + print(f"[{x}] test: end") + return x + + + async def main() -> None: + start = time.perf_counter() + async with anyio.create_task_group() as task_group: + for i in range(4): + task_group.start_soon(test, i) + end = time.perf_counter() + assert end - start < 1.1 + + + if __name__ == "__main__": + anyio.run(main) """ from async_wrapper.convert._sync.main import Sync diff --git a/src/async_wrapper/convert/_sync/main.py b/src/async_wrapper/convert/_sync/main.py index 0ad3850..e0a9061 100644 --- a/src/async_wrapper/convert/_sync/main.py +++ b/src/async_wrapper/convert/_sync/main.py @@ -15,8 +15,8 @@ if TYPE_CHECKING: from collections.abc import Awaitable, Callable -ValueT = TypeVar("ValueT", infer_variance=True) -ParamT = ParamSpec("ParamT") +_T = TypeVar("_T", infer_variance=True) +_P = ParamSpec("_P") __all__ = ["async_to_sync"] @@ -27,16 +27,16 @@ ) -class Sync(Generic[ParamT, ValueT]): - def __init__(self, func: Callable[ParamT, Awaitable[ValueT]]) -> None: +class Sync(Generic[_P, _T]): + def __init__(self, func: Callable[_P, Awaitable[_T]]) -> None: self._func = func @cached_property - def _wrapped(self) -> Callable[ParamT, ValueT]: + def _wrapped(self) -> Callable[_P, _T]: sync_func = _as_sync(self._func) @wraps(self._func) - def inner(*args: ParamT.args, **kwargs: ParamT.kwargs) -> ValueT: + def inner(*args: _P.args, **kwargs: _P.kwargs) -> _T: if not _running_in_async_context(): return _run(self._func, *args, **kwargs) @@ -52,23 +52,23 @@ def inner(*args: ParamT.args, **kwargs: ParamT.kwargs) -> ValueT: return inner - def __call__(self, *args: ParamT.args, **kwargs: ParamT.kwargs) -> ValueT: + def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> _T: return self._wrapped(*args, **kwargs) @overload def async_to_sync( - func_or_awaitable: Callable[ParamT, Awaitable[ValueT]], -) -> Callable[ParamT, ValueT]: ... + func_or_awaitable: Callable[_P, Awaitable[_T]], +) -> Callable[_P, _T]: ... @overload -def async_to_sync(func_or_awaitable: Awaitable[ValueT]) -> Callable[[], ValueT]: ... +def async_to_sync(func_or_awaitable: Awaitable[_T]) -> Callable[[], _T]: ... @overload def async_to_sync( - func_or_awaitable: Callable[..., Awaitable[ValueT]] | Awaitable[ValueT], -) -> Callable[..., ValueT]: ... + func_or_awaitable: Callable[..., Awaitable[_T]] | Awaitable[_T], +) -> Callable[..., _T]: ... def async_to_sync( - func_or_awaitable: Callable[ParamT, Awaitable[ValueT]] | Awaitable[ValueT], -) -> Callable[ParamT, ValueT] | Callable[[], ValueT]: + func_or_awaitable: Callable[_P, Awaitable[_T]] | Awaitable[_T], +) -> Callable[_P, _T] | Callable[[], _T]: """ Convert an awaitable function or awaitable object to a synchronous function. @@ -82,61 +82,58 @@ def async_to_sync( A synchronous function. Example: - >>> import asyncio - >>> import time - >>> - >>> import anyio - >>> import sniffio - >>> - >>> from async_wrapper import async_to_sync - >>> - >>> - >>> @async_to_sync - >>> async def test(x: int) -> int: - >>> backend = sniffio.current_async_library() - >>> if backend == "asyncio": - >>> loop = asyncio.get_running_loop() - >>> print(backend, loop) - >>> else: - >>> print(backend) - >>> await anyio.sleep(1) - >>> return x - >>> - >>> - >>> def main() -> None: - >>> start = time.perf_counter() - >>> result = test(1) - >>> end = time.perf_counter() - >>> assert result == 1 - >>> assert end - start < 1.1 - >>> - >>> - >>> async def async_main() -> None: - >>> start = time.perf_counter() - >>> result = test(1) - >>> end = time.perf_counter() - >>> assert result == 1 - >>> assert end - start < 1.1 - >>> - >>> - >>> if __name__ == "__main__": - >>> main() - >>> anyio.run( - >>> async_main, - >>> backend="asyncio", - >>> backend_options={"use_uvloop": True}, - >>> ) - >>> anyio.run( - >>> async_main, - >>> backend="asyncio", - >>> backend_options={"use_uvloop": True}, - >>> ) - >>> anyio.run(async_main, backend="trio") - $ poetry run python main.py - asyncio <_UnixSelectorEventLoop running=True closed=False debug=False> - asyncio <_UnixSelectorEventLoop running=True closed=False debug=False> - asyncio - trio + .. code-block:: python + + import asyncio + import time + + import anyio + import sniffio + + from async_wrapper import async_to_sync + + + @async_to_sync + async def test(x: int) -> int: + backend = sniffio.current_async_library() + if backend == "asyncio": + loop = asyncio.get_running_loop() + print(backend, loop) + else: + print(backend) + await anyio.sleep(1) + return x + + + def main() -> None: + start = time.perf_counter() + result = test(1) + end = time.perf_counter() + assert result == 1 + assert end - start < 1.1 + + + async def async_main() -> None: + start = time.perf_counter() + result = test(1) + end = time.perf_counter() + assert result == 1 + assert end - start < 1.1 + + + if __name__ == "__main__": + main() + anyio.run( + async_main, + backend="asyncio", + backend_options={"use_uvloop": True}, + ) + anyio.run( + async_main, + backend="asyncio", + backend_options={"use_uvloop": True}, + ) + anyio.run(async_main, backend="trio") """ if callable(func_or_awaitable): from async_wrapper.convert._async import Async @@ -154,13 +151,11 @@ def async_to_sync( return _async_func_to_sync(awaitable_func) -def _async_func_to_sync( - func: Callable[ParamT, Awaitable[ValueT]], -) -> Callable[ParamT, ValueT]: +def _async_func_to_sync(func: Callable[_P, Awaitable[_T]]) -> Callable[_P, _T]: sync_func = _as_sync(func) @wraps(func) - def inner(*args: ParamT.args, **kwargs: ParamT.kwargs) -> ValueT: + def inner(*args: _P.args, **kwargs: _P.kwargs) -> _T: if not _running_in_async_context(): return _run(func, *args, **kwargs) @@ -177,19 +172,15 @@ def inner(*args: ParamT.args, **kwargs: ParamT.kwargs) -> ValueT: return inner -def _as_sync(func: Callable[ParamT, Awaitable[ValueT]]) -> Callable[ParamT, ValueT]: +def _as_sync(func: Callable[_P, Awaitable[_T]]) -> Callable[_P, _T]: @wraps(func) - def inner(*args: ParamT.args, **kwargs: ParamT.kwargs) -> ValueT: + def inner(*args: _P.args, **kwargs: _P.kwargs) -> _T: return _run(func, *args, **kwargs) return inner -def _run( - func: Callable[ParamT, Awaitable[ValueT]], - *args: ParamT.args, - **kwargs: ParamT.kwargs, -) -> ValueT: +def _run(func: Callable[_P, Awaitable[_T]], *args: _P.args, **kwargs: _P.kwargs) -> _T: backend = _get_current_backend() new_func = partial(func, *args, **kwargs) backend_options: dict[str, Any] = {} @@ -232,8 +223,8 @@ def _init(backend: str, use_uvloop: bool) -> None: # noqa: FBT001 use_uvloop_var.set(use_uvloop) -def _awaitable_to_function(value: Awaitable[ValueT]) -> Callable[[], Awaitable[ValueT]]: - async def awaitable() -> ValueT: +def _awaitable_to_function(value: Awaitable[_T]) -> Callable[[], Awaitable[_T]]: + async def awaitable() -> _T: return await value return awaitable diff --git a/src/async_wrapper/convert/_sync/sqlalchemy.py b/src/async_wrapper/convert/_sync/sqlalchemy.py index 7eb493b..a27a35a 100644 --- a/src/async_wrapper/convert/_sync/sqlalchemy.py +++ b/src/async_wrapper/convert/_sync/sqlalchemy.py @@ -11,7 +11,7 @@ import greenlet -ValueT = TypeVar("ValueT", infer_variance=True) +_T = TypeVar("_T", infer_variance=True) _SA_GREENLET_ATTR = "__sqlalchemy_greenlet_provider__" @@ -38,7 +38,7 @@ def _check_sa_current_greenlet() -> bool: return _check_sa_greenlet(current) -def run_sa_greenlet(awaitable: Awaitable[ValueT]) -> ValueT | Unset: +def run_sa_greenlet(awaitable: Awaitable[_T]) -> _T | Unset: with suppress(ImportError): if _check_sa_current_greenlet(): return _wait_sa_greenlet(awaitable) @@ -46,7 +46,7 @@ def run_sa_greenlet(awaitable: Awaitable[ValueT]) -> ValueT | Unset: return unset -def _wait_sa_greenlet(awaitable: Awaitable[ValueT]) -> ValueT: +def _wait_sa_greenlet(awaitable: Awaitable[_T]) -> _T: try: from sqlalchemy.util import await_only except ImportError as exc: # pragma: no cover diff --git a/src/async_wrapper/convert/main.py b/src/async_wrapper/convert/main.py index 731688f..d9ce144 100644 --- a/src/async_wrapper/convert/main.py +++ b/src/async_wrapper/convert/main.py @@ -12,28 +12,28 @@ if TYPE_CHECKING: from collections.abc import Awaitable, Callable, Coroutine -ValueT = TypeVar("ValueT", infer_variance=True) -ParamT = ParamSpec("ParamT") +_T = TypeVar("_T", infer_variance=True) +_P = ParamSpec("_P") __all__ = ["toggle_func", "async_to_sync", "sync_to_async"] @overload def toggle_func( - func: Callable[ParamT, Coroutine[Any, Any, ValueT]], -) -> Callable[ParamT, ValueT]: ... # pragma: no cover + func: Callable[_P, Coroutine[Any, Any, _T]], +) -> Callable[_P, _T]: ... # pragma: no cover @overload def toggle_func( - func: Callable[ParamT, ValueT], -) -> Callable[ParamT, Awaitable[ValueT]]: ... # pragma: no cover + func: Callable[_P, _T], +) -> Callable[_P, Awaitable[_T]]: ... # pragma: no cover # TODO: Coroutine -> Awaitable def toggle_func( - func: Callable[ParamT, ValueT] | Callable[ParamT, Coroutine[Any, Any, ValueT]], -) -> Callable[ParamT, ValueT] | Callable[ParamT, Awaitable[ValueT]]: + func: Callable[_P, _T] | Callable[_P, Coroutine[Any, Any, _T]], +) -> Callable[_P, _T] | Callable[_P, Awaitable[_T]]: """ Convert between synchronous and asynchronous functions. diff --git a/src/async_wrapper/pipe.py b/src/async_wrapper/pipe.py index dd70cab..d7c6fe8 100644 --- a/src/async_wrapper/pipe.py +++ b/src/async_wrapper/pipe.py @@ -30,12 +30,12 @@ class Synchronization(TypedDict, total=False): "create_disposable", ] -InputT = TypeVar("InputT", infer_variance=True) -OutputT = TypeVar("OutputT", infer_variance=True) +_T = TypeVar("_T", infer_variance=True) +_T2 = TypeVar("_T2", infer_variance=True) @runtime_checkable -class Disposable(Protocol[InputT, OutputT]): +class Disposable(Protocol[_T, _T2]): """Defines the interface for a disposable resource.""" @property @@ -43,7 +43,7 @@ def is_disposed(self) -> bool: """Check if disposed""" ... # pragma: no cover - async def next(self, value: InputT) -> OutputT: + async def next(self, value: _T) -> _T2: """ Processes the next input value and produces an output value. @@ -63,10 +63,10 @@ def __hash__(self) -> int: ... @runtime_checkable -class DisposableWithCallback(Disposable[InputT, OutputT], Protocol[InputT, OutputT]): +class DisposableWithCallback(Disposable[_T, _T2], Protocol[_T, _T2]): """disposable & callback""" - def prepare_callback(self, subscribable: Subscribable[InputT, OutputT]) -> Any: + def prepare_callback(self, subscribable: Subscribable[_T, _T2]) -> Any: """Prepare a callback to use when dispose is executed. Args: @@ -75,7 +75,7 @@ def prepare_callback(self, subscribable: Subscribable[InputT, OutputT]) -> Any: @runtime_checkable -class Subscribable(Disposable[InputT, OutputT], Protocol[InputT, OutputT]): +class Subscribable(Disposable[_T, _T2], Protocol[_T, _T2]): """subscribable & disposable""" @property @@ -85,7 +85,7 @@ def size(self) -> int: def subscribe( self, - disposable: Disposable[OutputT, Any] | Callable[[OutputT], Awaitable[Any]], + disposable: Disposable[_T2, Any] | Callable[[_T2], Awaitable[Any]], *, dispose: bool = True, ) -> Any: @@ -106,15 +106,13 @@ def unsubscribe(self, disposable: Disposable[Any, Any]) -> None: """ -class SimpleDisposable( - DisposableWithCallback[InputT, OutputT], Generic[InputT, OutputT] -): +class SimpleDisposable(DisposableWithCallback[_T, _T2], Generic[_T, _T2]): """simple disposable impl.""" - _journals: deque[Subscribable[InputT, OutputT]] + _journals: deque[Subscribable[_T, _T2]] __slots__ = ("_func", "_is_disposed", "_journals", "_async_lock", "_thread_lock") - def __init__(self, func: Callable[[InputT], Awaitable[OutputT]]) -> None: + def __init__(self, func: Callable[[_T], Awaitable[_T2]]) -> None: self._func = func self._is_disposed = False self._journals = deque() @@ -127,7 +125,7 @@ def is_disposed(self) -> bool: return self._is_disposed @override - async def next(self, value: InputT) -> OutputT: + async def next(self, value: _T) -> _T2: if self._is_disposed: raise AlreadyDisposedError("disposable already disposed") return await self._func(value) @@ -141,7 +139,7 @@ async def dispose(self) -> Any: self._is_disposed = True @override - def prepare_callback(self, subscribable: Subscribable[InputT, OutputT]) -> Any: + def prepare_callback(self, subscribable: Subscribable[_T, _T2]) -> Any: if self._is_disposed: raise AlreadyDisposedError("disposable already disposed") @@ -153,7 +151,7 @@ def __hash__(self) -> int: return hash((id(self), id(self._func))) -class Pipe(Subscribable[InputT, OutputT], Generic[InputT, OutputT]): +class Pipe(Subscribable[_T, _T2], Generic[_T, _T2]): """ Implements a pipe that can be used to communicate data between coroutines. @@ -164,8 +162,8 @@ class Pipe(Subscribable[InputT, OutputT], Generic[InputT, OutputT]): """ _context: Synchronization - _listener: Callable[[InputT], Awaitable[OutputT]] - _listeners: dict[Disposable[OutputT, Any], bool] + _listener: Callable[[_T], Awaitable[_T2]] + _listeners: dict[Disposable[_T2, Any], bool] _dispose: Callable[[], Awaitable[Any]] | None _is_disposed: bool _dispose_lock: Lock @@ -181,7 +179,7 @@ class Pipe(Subscribable[InputT, OutputT], Generic[InputT, OutputT]): def __init__( self, - listener: Callable[[InputT], Awaitable[OutputT]], + listener: Callable[[_T], Awaitable[_T2]], context: Synchronization | None = None, dispose: Callable[[], Awaitable[Any]] | None = None, ) -> None: @@ -203,7 +201,7 @@ def size(self) -> int: return len(self._listeners) @override - async def next(self, value: InputT) -> OutputT: + async def next(self, value: _T) -> _T2: if self._is_disposed: raise AlreadyDisposedError("pipe already disposed") @@ -235,7 +233,7 @@ async def dispose(self) -> None: @override def subscribe( self, - disposable: Disposable[OutputT, Any] | Callable[[OutputT], Awaitable[Any]], + disposable: Disposable[_T2, Any] | Callable[[_T2], Awaitable[Any]], *, dispose: bool = True, ) -> None: @@ -258,8 +256,8 @@ def __hash__(self) -> int: def create_disposable( - func: Callable[[InputT], Awaitable[OutputT]], -) -> SimpleDisposable[InputT, OutputT]: + func: Callable[[_T], Awaitable[_T2]], +) -> SimpleDisposable[_T, _T2]: """SimpleDisposable shortcut Args: @@ -286,7 +284,7 @@ async def _enter_context(stack: AsyncExitStack, context: Synchronization) -> Non async def _call_next( - context: Synchronization, disposable: Disposable[InputT, Any], value: InputT + context: Synchronization, disposable: Disposable[_T, Any], value: _T ) -> None: async with AsyncExitStack() as stack: await _enter_context(stack, context) diff --git a/src/async_wrapper/queue.py b/src/async_wrapper/queue.py index 88e793d..392ed7b 100644 --- a/src/async_wrapper/queue.py +++ b/src/async_wrapper/queue.py @@ -38,44 +38,46 @@ __all__ = ["Queue", "create_queue"] -ValueT = TypeVar("ValueT", infer_variance=True) +_T = TypeVar("_T", infer_variance=True) -class Queue(Generic[ValueT]): +class Queue(Generic[_T]): """ obtained from :class:`asyncio.Queue` Example: - >>> from __future__ import annotations - >>> - >>> from typing import Any - >>> - >>> import anyio - >>> - >>> from async_wrapper import Queue - >>> - >>> - >>> async def aput(queue: Queue[Any], value: Any) -> None: - >>> async with queue: - >>> await queue.aput(value) - >>> - >>> - >>> async def main() -> None: - >>> queue: Queue[Any] = Queue(10) - >>> - >>> async with anyio.create_task_group() as task_group: - >>> async with queue.aputter: - >>> for i in range(10): - >>> task_group.start_soon(aput, queue.clone.putter, i) - >>> - >>> async with queue.agetter: - >>> result = {x async for x in queue} - >>> - >>> assert result == set(range(10)) - >>> - >>> - >>> if __name__ == "__main__": - >>> anyio.run(main) + .. code-block:: python + + from __future__ import annotations + + from typing import Any + + import anyio + + from async_wrapper import Queue + + + async def aput(queue: Queue[Any], value: Any) -> None: + async with queue: + await queue.aput(value) + + + async def main() -> None: + queue: Queue[Any] = Queue(10) + + async with anyio.create_task_group() as task_group: + async with queue.aputter: + for i in range(10): + task_group.start_soon(aput, queue.clone.putter, i) + + async with queue.agetter: + result = {x async for x in queue} + + assert result == set(range(10)) + + + if __name__ == "__main__": + anyio.run(main) """ __slots__ = ("_putter", "_getter", "_close_putter", "_close_getter") @@ -84,10 +86,10 @@ class Queue(Generic[ValueT]): def __init__(self, max_size: float | None = None) -> None: ... @property - def _putter(self) -> MemoryObjectSendStream[ValueT]: ... + def _putter(self) -> MemoryObjectSendStream[_T]: ... @property - def _getter(self) -> MemoryObjectReceiveStream[ValueT]: ... + def _getter(self) -> MemoryObjectReceiveStream[_T]: ... else: @@ -95,9 +97,7 @@ def __init__( self, max_size: float | None = None, *, - _stream: tuple[ - MemoryObjectSendStream[ValueT], MemoryObjectReceiveStream[ValueT] - ] + _stream: tuple[MemoryObjectSendStream[_T], MemoryObjectReceiveStream[_T]] | None = None, ) -> None: self._init(max_size, _stream=_stream) @@ -106,9 +106,7 @@ def _init( self, max_size: float | None = None, *, - _stream: tuple[ - MemoryObjectSendStream[ValueT], MemoryObjectReceiveStream[ValueT] - ] + _stream: tuple[MemoryObjectSendStream[_T], MemoryObjectReceiveStream[_T]] | None = None, ) -> None: if _stream is None: @@ -199,7 +197,7 @@ def full(self) -> bool: return False return self.qsize() >= self.maxsize - async def aget(self, *, timeout: float | None = None) -> ValueT: + async def aget(self, *, timeout: float | None = None) -> _T: """ remove and return an item from the queue. @@ -212,7 +210,7 @@ async def aget(self, *, timeout: float | None = None) -> ValueT: with fail_after(timeout): return await self._aget() - async def aput(self, value: ValueT, *, timeout: float | None = None) -> None: + async def aput(self, value: _T, *, timeout: float | None = None) -> None: """ put an item into the queue. @@ -223,7 +221,7 @@ async def aput(self, value: ValueT, *, timeout: float | None = None) -> None: with fail_after(timeout): await self._aput(value) - async def _aget(self) -> ValueT: + async def _aget(self) -> _T: """remove and return an item from the queue.""" try: return await self._getter.receive() @@ -234,7 +232,7 @@ async def _aget(self) -> ValueT: except (ClosedResourceError, BrokenResourceError) as exc: raise QueueBrokenError from exc - async def _aput(self, value: ValueT) -> None: + async def _aput(self, value: _T) -> None: """put an item into the queue.""" try: await self._putter.send(value) @@ -245,7 +243,7 @@ async def _aput(self, value: ValueT) -> None: except (ClosedResourceError, BrokenResourceError) as exc: raise QueueBrokenError from exc - def get(self) -> ValueT: + def get(self) -> _T: """remove and return an item from the queue without blocking.""" try: return self._getter.receive_nowait() @@ -256,7 +254,7 @@ def get(self) -> ValueT: except (ClosedResourceError, BrokenResourceError) as exc: raise QueueBrokenError from exc - def put(self, value: ValueT) -> None: + def put(self, value: _T) -> None: """put an item into the queue without blocking.""" try: self._putter.send_nowait(value) @@ -305,7 +303,7 @@ def _close(self) -> None: self._putter.close() @property - def clone(self) -> _Clone[ValueT]: + def clone(self) -> _Clone[_T]: """ Create a queue factory for generating RestrictedQueue instances. @@ -316,7 +314,7 @@ def clone(self) -> _Clone[ValueT]: """ return _Clone(self) - def _clone(self, *, putter: bool, getter: bool) -> Queue[ValueT]: + def _clone(self, *, putter: bool, getter: bool) -> Queue[_T]: """create clone of this queue""" if self._closed: raise QueueClosedError("the queue is already closed") @@ -329,7 +327,7 @@ def _clone(self, *, putter: bool, getter: bool) -> Queue[ValueT]: except (ClosedResourceError, BrokenResourceError) as exc: raise QueueBrokenError from exc - new: Queue[ValueT] + new: Queue[_T] new = Queue(_stream=(_putter, _getter)) # type: ignore new._close_putter = putter # noqa: SLF001 new._close_getter = getter # noqa: SLF001 @@ -372,7 +370,7 @@ async def __aexit__( def __aiter__(self) -> Self: return self - async def __anext__(self) -> ValueT: + async def __anext__(self) -> _T: try: return await self.aget() except ( @@ -386,7 +384,7 @@ async def __anext__(self) -> ValueT: def __iter__(self) -> Self: return self - def __next__(self) -> ValueT: + def __next__(self) -> _T: try: return self.get() except ( @@ -407,10 +405,10 @@ def __repr__(self) -> str: return _render("Queue", max=max_size, size=size) -class _RestrictedQueue(Queue[ValueT], Generic[ValueT]): +class _RestrictedQueue(Queue[_T], Generic[_T]): __slots__ = ("_queue", "_do_putter", "_do_getter") - def __init__(self, queue: Queue[ValueT], *, putter: bool, getter: bool) -> None: + def __init__(self, queue: Queue[_T], *, putter: bool, getter: bool) -> None: self._queue = queue if getter is putter: raise QueueRestrictedError("putter and getter are the same") @@ -419,13 +417,13 @@ def __init__(self, queue: Queue[ValueT], *, putter: bool, getter: bool) -> None: @property @override - def _putter(self) -> MemoryObjectSendStream[ValueT]: + def _putter(self) -> MemoryObjectSendStream[_T]: self._raise_restricted(putter=True) return self._queue._putter # noqa: SLF001 @property @override - def _getter(self) -> MemoryObjectReceiveStream[ValueT]: + def _getter(self) -> MemoryObjectReceiveStream[_T]: self._raise_restricted(getter=True) return self._queue._getter # noqa: SLF001 @@ -448,9 +446,7 @@ def _close_stream(self) -> bool: raise RuntimeError("never") # pragma: no cover @property - def _stream( - self, - ) -> MemoryObjectSendStream[ValueT] | MemoryObjectReceiveStream[ValueT]: + def _stream(self) -> MemoryObjectSendStream[_T] | MemoryObjectReceiveStream[_T]: if self._do_getter: return self._getter if self._do_putter: @@ -485,7 +481,7 @@ def clone(self) -> NoReturn: raise TypeError("do not clone restricted queue") @override - def _clone(self, *, putter: bool, getter: bool) -> Queue[ValueT]: + def _clone(self, *, putter: bool, getter: bool) -> Queue[_T]: raise TypeError("do not clone restricted queue") @override @@ -537,15 +533,15 @@ def __repr__(self) -> str: return _render("RestrictedQueue", max=max_size, size=size, where=where) -class _Clone(Generic[ValueT]): +class _Clone(Generic[_T]): __slots__ = ("_queue",) - def __init__(self, queue: Queue[ValueT]) -> None: + def __init__(self, queue: Queue[_T]) -> None: if queue._closed: # noqa: SLF001 raise QueueClosedError("queue is already closed") self._queue = queue - def create(self, where: Literal["putter", "getter"]) -> _RestrictedQueue[ValueT]: + def create(self, where: Literal["putter", "getter"]) -> _RestrictedQueue[_T]: if where == "putter": return self.putter if where == "getter": @@ -553,13 +549,13 @@ def create(self, where: Literal["putter", "getter"]) -> _RestrictedQueue[ValueT] raise RuntimeError("never") # pragma: no cover @property - def putter(self) -> _RestrictedQueue[ValueT]: + def putter(self) -> _RestrictedQueue[_T]: self._raise_if_closed() new = self._queue._clone(putter=True, getter=False) # noqa: SLF001 return _RestrictedQueue(new, putter=True, getter=False) @property - def getter(self) -> _RestrictedQueue[ValueT]: + def getter(self) -> _RestrictedQueue[_T]: self._raise_if_closed() new = self._queue._clone(getter=True, putter=False) # noqa: SLF001 return _RestrictedQueue(new, putter=False, getter=True) diff --git a/src/async_wrapper/task_group/task_group.py b/src/async_wrapper/task_group/task_group.py index 4c0d2fb..7c1c724 100644 --- a/src/async_wrapper/task_group/task_group.py +++ b/src/async_wrapper/task_group/task_group.py @@ -16,8 +16,8 @@ from anyio.abc import CancelScope, CapacityLimiter, Lock, Semaphore -ValueT = TypeVar("ValueT", infer_variance=True) -ParamT = ParamSpec("ParamT") +_T = TypeVar("_T", infer_variance=True) +_P = ParamSpec("_P") __all__ = ["TaskGroupWrapper", "create_task_group_wrapper"] @@ -27,31 +27,33 @@ class TaskGroupWrapper(_TaskGroup): wrap :class:`anyio.abc.TaskGroup` Example: - >>> import anyio - >>> - >>> from async_wrapper import TaskGroupWrapper - >>> - >>> - >>> async def test(x: int) -> int: - >>> await anyio.sleep(0.1) - >>> return x - >>> - >>> - >>> async def main() -> None: - >>> async with anyio.create_task_group() as task_group: - >>> async with TaskGroupWrapper(task_group) as tg: - >>> func = tg.wrap(test) - >>> soon_1 = func(1) - >>> soon_2 = func(2) - >>> - >>> assert soon_1.is_ready - >>> assert soon_2.is_ready - >>> assert soon_1.value == 1 - >>> assert soon_2.value == 2 - >>> - >>> - >>> if __name__ == "__main__": - >>> anyio.run(main) + .. code-block:: python + + import anyio + + from async_wrapper import TaskGroupWrapper + + + async def test(x: int) -> int: + await anyio.sleep(0.1) + return x + + + async def main() -> None: + async with anyio.create_task_group() as task_group: + async with TaskGroupWrapper(task_group) as tg: + func = tg.wrap(test) + soon_1 = func(1) + soon_2 = func(2) + + assert soon_1.is_ready + assert soon_2.is_ready + assert soon_1.value == 1 + assert soon_2.value == 2 + + + if __name__ == "__main__": + anyio.run(main) """ __slots__ = ("_task_group", "_active_self") @@ -101,11 +103,11 @@ async def __aexit__( def wrap( self, - func: Callable[ParamT, Awaitable[ValueT]], + func: Callable[_P, Awaitable[_T]], semaphore: Semaphore | None = None, limiter: CapacityLimiter | None = None, lock: Lock | None = None, - ) -> SoonWrapper[ParamT, ValueT]: + ) -> SoonWrapper[_P, _T]: """ Wrap a function to be used within a wrapper. @@ -123,14 +125,14 @@ def wrap( return SoonWrapper(func, self, semaphore=semaphore, limiter=limiter, lock=lock) -class SoonWrapper(Generic[ParamT, ValueT]): +class SoonWrapper(Generic[_P, _T]): """wrapped func using in :class:`TaskGroupWrapper`""" __slots__ = ("func", "task_group", "semaphore", "limiter", "lock", "_wrapped") def __init__( self, - func: Callable[ParamT, Awaitable[ValueT]], + func: Callable[_P, Awaitable[_T]], task_group: _TaskGroup, semaphore: Semaphore | None = None, limiter: CapacityLimiter | None = None, @@ -144,10 +146,8 @@ def __init__( self._wrapped = None - def __call__( - self, *args: ParamT.args, **kwargs: ParamT.kwargs - ) -> SoonValue[ValueT]: - value: SoonValue[ValueT] = SoonValue() + def __call__(self, *args: _P.args, **kwargs: _P.kwargs) -> SoonValue[_T]: + value: SoonValue[_T] = SoonValue() wrapped = partial(self.wrapped, value, *args, **kwargs) self.task_group.start_soon(wrapped) return value @@ -155,15 +155,15 @@ def __call__( @property def wrapped( self, - ) -> Callable[Concatenate[SoonValue[ValueT], ParamT], Coroutine[Any, Any, ValueT]]: + ) -> Callable[Concatenate[SoonValue[_T], _P], Coroutine[Any, Any, _T]]: """wrapped func using semaphore""" if self._wrapped is not None: return self._wrapped @wraps(self.func) async def wrapped( - value: SoonValue[ValueT], *args: ParamT.args, **kwargs: ParamT.kwargs - ) -> ValueT: + value: SoonValue[_T], *args: _P.args, **kwargs: _P.kwargs + ) -> _T: async with AsyncExitStack() as stack: if self.semaphore is not None: await stack.enter_async_context(self.semaphore) diff --git a/src/async_wrapper/task_group/value.py b/src/async_wrapper/task_group/value.py index 6a918d9..e20381a 100644 --- a/src/async_wrapper/task_group/value.py +++ b/src/async_wrapper/task_group/value.py @@ -7,19 +7,19 @@ from async_wrapper.exception import PendingError -ValueT = TypeVar("ValueT", infer_variance=True) +_T = TypeVar("_T", infer_variance=True) Pending = local() __all__ = ["SoonValue"] -class SoonValue(Generic[ValueT]): +class SoonValue(Generic[_T]): """A class representing a value that will be available soon.""" __slots__ = ("_value",) def __init__(self) -> None: - self._value: ValueT | local = Pending + self._value: _T | local = Pending @override def __repr__(self) -> str: @@ -27,7 +27,7 @@ def __repr__(self) -> str: return f"" @property - def value(self) -> ValueT: + def value(self) -> _T: """ Gets the soon-to-be available value. diff --git a/src/async_wrapper/wait.py b/src/async_wrapper/wait.py index 14b144a..86c5504 100644 --- a/src/async_wrapper/wait.py +++ b/src/async_wrapper/wait.py @@ -19,8 +19,8 @@ __all__ = ["Waiter", "Completed", "wait_for"] -ValueT = TypeVar("ValueT", infer_variance=True) -ParamT = ParamSpec("ParamT") +_T = TypeVar("_T", infer_variance=True) +_P = ParamSpec("_P") class Waiter(Event): @@ -28,36 +28,33 @@ class Waiter(Event): wait wrapper Example: - >>> import anyio - >>> - >>> from async_wrapper import Waiter - >>> - >>> - >>> async def test() -> None: - >>> print("test: start") - >>> await anyio.sleep(1) - >>> print("test: end") - >>> - >>> - >>> async def test2(event: anyio.Event) -> None: - >>> print("test2: start") - >>> await event.wait() - >>> print("test2: end") - >>> - >>> - >>> async def main() -> None: - >>> async with anyio.create_task_group() as task_group: - >>> event = Waiter(test)(task_group) - >>> task_group.start_soon(test2, event) - >>> - >>> - >>> if __name__ == "__main__": - >>> anyio.run(main) - $ poetry run python main.py - test: start - test2: start - test: end - test2: end + .. code-block:: python + + import anyio + + from async_wrapper import Waiter + + + async def test() -> None: + print("test: start") + await anyio.sleep(1) + print("test: end") + + + async def test2(event: anyio.Event) -> None: + print("test2: start") + await event.wait() + print("test2: end") + + + async def main() -> None: + async with anyio.create_task_group() as task_group: + event = Waiter(test)(task_group) + task_group.start_soon(test2, event) + + + if __name__ == "__main__": + anyio.run(main) """ __slots__ = ("_event", "_func", "_args", "_kwargs") @@ -65,10 +62,7 @@ class Waiter(Event): _event: Event def __init__( - self, - func: Callable[ParamT, Awaitable[Any]], - *args: ParamT.args, - **kwargs: ParamT.kwargs, + self, func: Callable[_P, Awaitable[Any]], *args: _P.args, **kwargs: _P.kwargs ) -> None: self._func = func self._args = args @@ -99,10 +93,7 @@ def copy(self, *args: Any, **kwargs: Any) -> Self: @override def __new__( - cls, - func: Callable[ParamT, Awaitable[Any]], - *args: ParamT.args, - **kwargs: ParamT.kwargs, + cls, func: Callable[_P, Awaitable[Any]], *args: _P.args, **kwargs: _P.kwargs ) -> Self: new = object.__new__(cls) new._event = super().__new__(cls) # noqa: SLF001 @@ -130,54 +121,56 @@ class Completed: like :func:`asyncio.as_completed` Example: - >>> from __future__ import annotations - >>> - >>> import anyio - >>> - >>> from async_wrapper import Completed - >>> - >>> - >>> async def test( - >>> x: int, - >>> sleep: float, - >>> result: list[int] | None = None, - >>> ) -> int: - >>> print(f"[{x}] test: start") - >>> await anyio.sleep(sleep) - >>> print(f"[{x}] test: end") - >>> if result is not None: - >>> result.append(x) - >>> return x - >>> - >>> - >>> async def main() -> None: - >>> result: list[int] = [] - >>> async with anyio.create_task_group() as task_group: - >>> task_group.start_soon(test, 1, 1, result) - >>> async with Completed(task_group) as completed: - >>> completed.start_soon(None, test, 2, 0.2) - >>> completed.start_soon(None, test, 3, 0.1) - >>> completed.start_soon(None, test, 4, 0.3) - >>> - >>> result.extend([value async for value in completed]) - >>> - >>> assert result == [3, 2, 4, 1] - >>> - >>> result = [] - >>> async with anyio.create_task_group() as task_group: - >>> task_group.start_soon(test, 1, 1, result) - >>> async with Completed() as completed: - >>> completed.start_soon(task_group, test, 2, 0.2) - >>> completed.start_soon(task_group, test, 3, 0.1) - >>> completed.start_soon(task_group, test, 4, 0.3) - >>> - >>> result.extend([value async for value in completed]) - >>> - >>> assert result == [3, 2, 4, 1] - >>> - >>> - >>> if __name__ == "__main__": - >>> anyio.run(main) + .. code-block:: python + + from __future__ import annotations + + import anyio + + from async_wrapper import Completed + + + async def test( + x: int, + sleep: float, + result: list[int] | None = None, + ) -> int: + print(f"[{x}] test: start") + await anyio.sleep(sleep) + print(f"[{x}] test: end") + if result is not None: + result.append(x) + return x + + + async def main() -> None: + result: list[int] = [] + async with anyio.create_task_group() as task_group: + task_group.start_soon(test, 1, 1, result) + async with Completed(task_group) as completed: + completed.start_soon(None, test, 2, 0.2) + completed.start_soon(None, test, 3, 0.1) + completed.start_soon(None, test, 4, 0.3) + + result.extend([value async for value in completed]) + + assert result == [3, 2, 4, 1] + + result = [] + async with anyio.create_task_group() as task_group: + task_group.start_soon(test, 1, 1, result) + async with Completed() as completed: + completed.start_soon(task_group, test, 2, 0.2) + completed.start_soon(task_group, test, 3, 0.1) + completed.start_soon(task_group, test, 4, 0.3) + + result.extend([value async for value in completed]) + + assert result == [3, 2, 4, 1] + + + if __name__ == "__main__": + anyio.run(main) """ __slots__ = ("_events", "__setter", "__getter", "__task_group") @@ -299,10 +292,10 @@ async def __anext__(self) -> Any: async def wait_for( event: Event | Iterable[Event], - func: Callable[ParamT, Awaitable[ValueT]], - *args: ParamT.args, - **kwargs: ParamT.kwargs, -) -> ValueT: + func: Callable[_P, Awaitable[_T]], + *args: _P.args, + **kwargs: _P.kwargs, +) -> _T: """ Wait for an event before executing an awaitable function. @@ -318,37 +311,34 @@ async def wait_for( The result of the executed function. Example: - >>> import anyio - >>> - >>> from async_wrapper import wait_for - >>> - >>> - >>> async def test() -> None: - >>> print("test: start") - >>> await anyio.sleep(1) - >>> print("test: end") - >>> - >>> - >>> async def test2(event: anyio.Event) -> None: - >>> print("test2: start") - >>> await event.wait() - >>> print("test2: end") - >>> - >>> - >>> async def main() -> None: - >>> event = anyio.Event() - >>> async with anyio.create_task_group() as task_group: - >>> task_group.start_soon(wait_for, event, test) - >>> task_group.start_soon(test2, event) - >>> - >>> - >>> if __name__ == "__main__": - >>> anyio.run(main) - $ poetry run python main.py - test: start - test2: start - test: end - test2: end + .. code-block:: python + + import anyio + + from async_wrapper import wait_for + + + async def test() -> None: + print("test: start") + await anyio.sleep(1) + print("test: end") + + + async def test2(event: anyio.Event) -> None: + print("test2: start") + await event.wait() + print("test2: end") + + + async def main() -> None: + event = anyio.Event() + async with anyio.create_task_group() as task_group: + task_group.start_soon(wait_for, event, test) + task_group.start_soon(test2, event) + + + if __name__ == "__main__": + anyio.run(main) """ event = set(event) if not isinstance(event, Event) else (event,) try: diff --git a/src/docs/__init__.py b/src/docs/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/src/docs/async_wrapper/convert.rst b/src/docs/async_wrapper/convert.rst index 2186f83..a663f40 100644 --- a/src/docs/async_wrapper/convert.rst +++ b/src/docs/async_wrapper/convert.rst @@ -2,6 +2,6 @@ async\_wrapper.convert module ============================== .. automodule:: async_wrapper.convert - :members: - :undoc-members: - :show-inheritance: + :members: + :undoc-members: + :show-inheritance: diff --git a/src/docs/async_wrapper/exception.rst b/src/docs/async_wrapper/exception.rst index 53334f7..3db5a8e 100644 --- a/src/docs/async_wrapper/exception.rst +++ b/src/docs/async_wrapper/exception.rst @@ -1,7 +1,7 @@ async\_wrapper.exception.py -------------------------------- +--------------------------- .. automodule:: async_wrapper.exception - :members: - :undoc-members: - :show-inheritance: \ No newline at end of file + :members: + :undoc-members: + :show-inheritance: \ No newline at end of file diff --git a/src/docs/async_wrapper/index.rst b/src/docs/async_wrapper/index.rst index 31061f1..73edb3d 100644 --- a/src/docs/async_wrapper/index.rst +++ b/src/docs/async_wrapper/index.rst @@ -2,11 +2,11 @@ async\_wrapper package ====================== .. toctree:: - :maxdepth: 2 + :maxdepth: 2 - convert - task_group - exception - queue - wait - pipe \ No newline at end of file + convert + task_group + exception + queue + wait + pipe \ No newline at end of file diff --git a/src/docs/async_wrapper/pipe.rst b/src/docs/async_wrapper/pipe.rst index 1c34ef6..259f1ae 100644 --- a/src/docs/async_wrapper/pipe.rst +++ b/src/docs/async_wrapper/pipe.rst @@ -1,7 +1,7 @@ async\_wrapper.pipe.py --------------------------- +---------------------- .. automodule:: async_wrapper.pipe - :members: - :undoc-members: - :show-inheritance: \ No newline at end of file + :members: + :undoc-members: + :show-inheritance: \ No newline at end of file diff --git a/src/docs/async_wrapper/queue.rst b/src/docs/async_wrapper/queue.rst index 082d618..c28af94 100644 --- a/src/docs/async_wrapper/queue.rst +++ b/src/docs/async_wrapper/queue.rst @@ -1,13 +1,13 @@ async\_wrapper.queue.py ---------------------------- +----------------------- .. automodule:: async_wrapper.queue - :members: - :undoc-members: - :show-inheritance: - :exclude-members: Queue + :members: + :undoc-members: + :show-inheritance: + :exclude-members: Queue - .. autoclass:: Queue(max_size: float | None = None) - :members: - :undoc-members: - :show-inheritance: \ No newline at end of file + .. autoclass:: Queue(max_size: float | None = None) + :members: + :undoc-members: + :show-inheritance: \ No newline at end of file diff --git a/src/docs/async_wrapper/task_group.rst b/src/docs/async_wrapper/task_group.rst index 586461b..9db2055 100644 --- a/src/docs/async_wrapper/task_group.rst +++ b/src/docs/async_wrapper/task_group.rst @@ -2,7 +2,7 @@ async\_wrapper.task\_group module ================================== .. automodule:: async_wrapper.task_group - :members: - :undoc-members: - :show-inheritance: - :exclude-members: spawn, start \ No newline at end of file + :members: + :undoc-members: + :show-inheritance: + :exclude-members: spawn, start \ No newline at end of file diff --git a/src/docs/async_wrapper/wait.rst b/src/docs/async_wrapper/wait.rst index fc7d956..8dacd25 100644 --- a/src/docs/async_wrapper/wait.rst +++ b/src/docs/async_wrapper/wait.rst @@ -1,7 +1,7 @@ async\_wrapper.wait.py --------------------------- +---------------------- .. automodule:: async_wrapper.wait - :members: - :undoc-members: - :show-inheritance: \ No newline at end of file + :members: + :undoc-members: + :show-inheritance: \ No newline at end of file diff --git a/src/docs/conf.py b/src/docs/conf.py index de012ba..a675445 100644 --- a/src/docs/conf.py +++ b/src/docs/conf.py @@ -2,57 +2,128 @@ # # For the full list of built-in configuration values, see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Project information ----------------------------------------------------- +# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information from __future__ import annotations import sys +from datetime import datetime, timedelta, timezone +from importlib.metadata import distribution from os import environ from pathlib import Path -src_dir = Path(__file__).resolve().parent.parent.parent / "src" -sys.path.insert(0, src_dir.as_posix()) +import tomli as toml -__version__ = environ.get("READTHEDOCS_GIT_IDENTIFIER", "") -if not __version__: - from async_wrapper import __version__ - -# -- Project information ----------------------------------------------------- -# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information +conf = Path(__file__).resolve() +sys.path.insert(0, str(conf.parent.parent)) +pyproject = conf.parent.parent.with_name("pyproject.toml") +with pyproject.open("rb") as f: + pyproject_dict = toml.load(f) -project = "async_wrapper" -copyright = "2023, phi.friday" # noqa: A001 -author = "phi.friday" -release = __version__ +project = pyproject_dict["project"]["name"] +dist = distribution(project) +author = "Choi Min-yeong" +kr_timezone = timezone(timedelta(hours=9)) +copyright = f"2023-{datetime.now(kr_timezone).year}, Choi Min-yeong" +release = environ.get("READTHEDOCS_GIT_IDENTIFIER", dist.version) +repo_url: str = pyproject_dict["project"]["urls"]["Repository"] # -- General configuration --------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration extensions = [ - "sphinx.ext.todo", + "myst_parser", "sphinx.ext.autodoc", "sphinx.ext.napoleon", - "sphinx.ext.intersphinx", - "sphinx_search.extension", - "sphinx_rtd_theme", - "sphinx_mdinclude", "sphinx.ext.viewcode", + "sphinx.ext.intersphinx", + "sphinx_autodoc_typehints", + "sphinx_immaterial", + "sphinx_immaterial.task_lists", ] templates_path = ["_templates"] exclude_patterns = [] -intersphinx_mapping = {"anyio": ("https://anyio.readthedocs.io/en/3.x/", None)} -autodoc_member_order = "bysource" # -- Options for HTML output ------------------------------------------------- # https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output -html_theme = "sphinx_rtd_theme" +html_theme = "sphinx_immaterial" html_static_path = ["_static"] - -# napoleon +html_theme_options = { + "repo_url": repo_url, + "repo_name": repo_url.removeprefix("https://github.com/"), + "icon": {"repo": "fontawesome/brands/github", "edit": "material/file-edit-outline"}, + "features": [ + "navigation.tabs", + "navigation.tabs.sticky", + "navigation.top", + "navigation.tracking", + "search.highlight", + "toc.follow", + ], + "palette": [ + { + "media": "(prefers-color-scheme)", + "toggle": { + "icon": "material/brightness-auto", + "name": "Switch to light mode", + }, + }, + { + "media": "(prefers-color-scheme: light)", + "scheme": "default", + "toggle": {"icon": "material/brightness-7", "name": "Switch to dark mode"}, + }, + { + "media": "(prefers-color-scheme: dark)", + "scheme": "slate", + "toggle": { + "icon": "material/brightness-4", + "name": "Switch to system preference", + }, + }, + ], +} +### sphinx.ext.intersphinx +intersphinx_mapping = { + "python": ("https://docs.python.org/3", None), + "anyio": ("https://anyio.readthedocs.io/en/4.x/", None), +} +### sphinx.ext.autodoc +autodoc_class_signature = "mixed" +autodoc_member_order = "bysource" +autodoc_docstring_signature = True +autodoc_typehints = "signature" +autodoc_typehints_description_target = "all" +autodoc_typehints_format = "short" +### sphinx.ext.napoleon napoleon_google_docstring = True -napoleon_include_init_with_doc = True +napoleon_numpy_docstring = False +napoleon_include_init_with_doc = False napoleon_include_private_with_doc = False +napoleon_include_special_with_doc = True +napoleon_use_admonition_for_examples = True +napoleon_use_admonition_for_notes = True +napoleon_use_admonition_for_references = True napoleon_use_ivar = False napoleon_use_param = True -napoleon_use_rtype = True +napoleon_use_rtype = False +napoleon_preprocess_types = True +napoleon_type_aliases = None +napoleon_attr_annotations = True +### autodoc-typehints +typehints_fully_qualified = False +always_document_param_types = False +always_use_bars_union = True +typehints_document_rtype = False +typehints_use_rtype = False +typehints_defaults = "braces" +simplify_optional_unions = True +typehints_formatter = None +typehints_use_signature = True +typehints_use_signature_return = True +### myst-parser +myst_gfm_only = True diff --git a/src/docs/index.rst b/src/docs/index.rst index d341e4f..803a764 100644 --- a/src/docs/index.rst +++ b/src/docs/index.rst @@ -2,16 +2,9 @@ Contents -========================================= +======== .. toctree:: - :maxdepth: 3 - - async_wrapper/index - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` + :maxdepth: 3 + + async_wrapper/index \ No newline at end of file diff --git a/src/tests/convert/test_sync.py b/src/tests/convert/test_sync.py index 993fc45..ad90a85 100644 --- a/src/tests/convert/test_sync.py +++ b/src/tests/convert/test_sync.py @@ -13,7 +13,7 @@ from tests.base import Timer from tests.convert.base import BaseTest -ValueT = TypeVar("ValueT", infer_variance=True) +_T = TypeVar("_T", infer_variance=True) class TestSync(BaseTest): @@ -62,17 +62,17 @@ async def test_async_to_sync_in_async(self, x: int): assert self.epsilon * x < timer.term < self.epsilon * x + self.epsilon -class AwaitableObject(Generic[ValueT]): - def __init__(self, value: ValueT) -> None: +class AwaitableObject(Generic[_T]): + def __init__(self, value: _T) -> None: self.value = value - def __await__(self) -> Generator[Any, None, ValueT]: + def __await__(self) -> Generator[Any, None, _T]: yield return self.value -def sample_coroutine(value: ValueT) -> Coroutine[Any, Any, ValueT]: - async def inner() -> ValueT: +def sample_coroutine(value: _T) -> Coroutine[Any, Any, _T]: + async def inner() -> _T: await checkpoint() return value diff --git a/src/tests/test_pipe.py b/src/tests/test_pipe.py index 6421d37..0314a4e 100644 --- a/src/tests/test_pipe.py +++ b/src/tests/test_pipe.py @@ -25,8 +25,8 @@ pytestmark = pytest.mark.anyio -ValueT = TypeVar("ValueT", infer_variance=True) -SubscribableT = TypeVar("SubscribableT", bound=Subscribable[Any, Any]) +_T = TypeVar("_T", infer_variance=True) +_ST = TypeVar("_ST", bound=Subscribable[Any, Any]) EPSILON: float = 0.1 @@ -121,12 +121,12 @@ def subscribable_type(request: pytest.FixtureRequest) -> type[Subscribable[Any, return request.param -async def as_tuple(value: ValueT) -> tuple[ValueT]: +async def as_tuple(value: _T) -> tuple[_T]: await checkpoint() return (value,) -async def return_self(value: ValueT) -> ValueT: +async def return_self(value: _T) -> _T: await checkpoint() return value @@ -529,6 +529,6 @@ async def test_simple_prepare_callback_after_disposed(): def _construct_subcribable( - subscribable_type: type[SubscribableT], *args: Any, **kwargs: Any -) -> SubscribableT: + subscribable_type: type[_ST], *args: Any, **kwargs: Any +) -> _ST: return subscribable_type(*args, **kwargs)