Source code for soulfire.transport
from __future__ import annotations
from collections.abc import AsyncIterator, Awaitable, Callable
from typing import Protocol, runtime_checkable
from connectrpc.errors import ConnectError
from effect_py import (
Effect,
EffectGen,
Scope,
acquire_release,
from_async,
gen,
try_,
try_async,
with_span,
)
from .errors import SoulFireOperationError, SoulFireRpcError, operation_error, rpc_error
from .streams import END, Cursor, End, Item, Stream
@runtime_checkable
class _Closable(Protocol):
async def aclose(self) -> None: ...
[docs]
def rpc[A](operation: str, call: Callable[[], Awaitable[A]]) -> Effect[A, SoulFireRpcError]:
def on_error(error: Exception) -> SoulFireRpcError:
if isinstance(error, SoulFireRpcError):
return error
if isinstance(error, ConnectError):
return rpc_error(operation, error)
raise error
return try_async(call, on_error).pipe(with_span(operation))
[docs]
def validate[A](call: Callable[[], A]) -> Effect[A, SoulFireOperationError]:
def on_error(error: Exception) -> SoulFireOperationError:
return operation_error("validation", error)
return try_(call, on_error)
[docs]
def rpc_stream[A](
operation: str, factory: Callable[[], AsyncIterator[A]]
) -> Stream[A, SoulFireOperationError]:
@gen
def acquire() -> EffectGen[Cursor[A, SoulFireOperationError], SoulFireOperationError, Scope]:
def create() -> AsyncIterator[A]:
return factory()
async def close(iterator: AsyncIterator[A]) -> None:
if isinstance(iterator, _Closable):
await iterator.aclose()
iterator = yield from acquire_release(
try_(create, lambda error: operation_error(operation, error)),
lambda iterator, _: from_async(lambda: close(iterator)),
)
async def pull() -> Item[A] | End:
try:
return Item(await anext(iterator))
except StopAsyncIteration:
return END
return Cursor(lambda: rpc(operation, pull))
return Stream(acquire)