async def subscribe_async( aobv: AsyncObserver[TSource]) -> AsyncDisposable: disposable = AsyncDisposable.empty() async def action(source: AsyncObservable[TSource]) -> None: nonlocal disposable async def asend(value: TSource) -> None: await aobv.asend(value) async def athrow(error: Exception) -> None: next_source = handler(error) await action(next_source) async def aclose() -> None: await aobv.aclose() _obv = AsyncAnonymousObserver(asend, athrow, aclose) await disposable.dispose_async() subscription = await source.subscribe_async(_obv) disposable = subscription await action(source) return AsyncDisposable.create(disposable.dispose_async)
async def autorun(func): async def run_func(): # TODO: write unit test for conditional observer. Something like: # if pair.y > 10: # print(pair.x) # and then change x when y is less than 10. prev_subs = ctx["subs"] ctx["subs"] = set() parent_ctx = obs_ctx.get() obs_ctx.set(ctx) await func() obs_ctx.set(parent_ctx) for sub in prev_subs - ctx["subs"]: await sub.dispose_async() ctx = { "observer": await BufferedObserver(dropargs(run_func)), "subs": set() } async def dispose_async(): nonlocal ctx, func for sub in ctx["subs"]: await sub.dispose_async() await ctx["observer"].dispose_async() del ctx del func await run_func() sub = AsyncDisposable.create(dispose_async) return sub
async def subscribe_async(observer: AsyncObserver[TSource]) -> AsyncDisposable: task: Optional[Future[None]] = None async def cancel(): if task is not None: task.cancel() sub = AsyncDisposable.create(cancel) async def worker() -> None: async for value in iterable: try: await observer.asend(value) except Exception as ex: await observer.athrow(ex) return await observer.aclose() try: task = asyncio.ensure_future(worker()) except Exception as ex: log.debug("FromIterable:worker(), Exception: %s" % ex) await observer.athrow(ex) return sub
def canceller() -> Tuple[AsyncDisposable, CancellationToken]: cts = CancellationTokenSource() async def cancel() -> None: log.debug("cancller, cancelling!") cts.cancel() return AsyncDisposable.create(cancel), cts.token
async def test_async_disposable_disposed_twice_calls_once(): called = [] async def action(): called.append(True) disp = AsyncDisposable.create(action) await disp.dispose_async() await disp.dispose_async() assert len(called) == 1
async def subscribe_async( self, observer: AsyncObserver[TSource]) -> AsyncDisposable: """Start streaming.""" self.check_disposed() self._observer = observer if not self._wait.done(): self._wait.set_result(True) return AsyncDisposable.create(self.dispose_async)
async def test_async_disposable_works(): called = [] async def action(): called.append(True) disp = AsyncDisposable.create(action) async with disp: assert not called assert called
async def test_async_disposable_disposed(): called = [] async def action(): called.append(True) disp = AsyncDisposable.create(action) await disp.dispose_async() assert called with pytest.raises(ObjectDisposedException): # type: ignore async with disp: assert not called assert called
async def subscribe_async( self, observer: AsyncObserver[TSource]) -> AsyncDisposable: """Subscribe.""" log.debug("AsyncMultiStream:subscribe_async()") self.check_disposed() self._observers.append(observer) async def dispose() -> None: log.debug("AsyncMultiStream:dispose()") if observer in self._observers: self._observers.remove(observer) return AsyncDisposable.create(dispose)
async def get(self): ctx = obs_ctx.get() if "observer" in ctx: observer = ctx["observer"] if not self.observers: await self.on_start() if observer not in self.observers: sub = await self.values.subscribe_async(observer) async def dispose_async(): await sub.dispose_async() del self.observers[observer] if not self.observers: await self.on_stop() self.observers[observer] = AsyncDisposable.create( dispose_async) ctx["subs"].add(self.observers[observer]) return self.current_value
async def subscribe_async(aobv: AsyncObserver[TSource]) -> AsyncDisposable: safe_obv, auto_detach = auto_detach_observer(aobv) def obv(mb: MailboxProcessor[Msg[TSource]], id: int): async def asend(value: TSource) -> None: await safe_obv.asend(value) async def athrow(error: Exception) -> None: await safe_obv.athrow(error) async def aclose() -> None: pipe(Key(id), InnerCompletedMsg, mb.post) return AsyncAnonymousObserver(asend, athrow, aclose) async def worker(inbox: MailboxProcessor[Msg[TSource]]) -> None: @tailrec_async async def message_loop(current: Option[AsyncDisposable], is_stopped: bool, current_id: int) -> TailCallResult[None]: cmd = await inbox.receive() with match(cmd) as case: for xs in case(InnerObservableMsg[TSource]): next_id = current_id + 1 for disp in current.to_list(): await disp.dispose_async() inner = await xs.subscribe_async(obv(inbox, next_id)) current, current_id = Some(inner), next_id break for idx in case(InnerCompletedMsg[Key]): if is_stopped and idx == current_id: await safe_obv.aclose() current, is_stopped = Nothing, True break while case(CompletedMsg): if current.is_none(): await safe_obv.aclose() break while case(DisposeMsg): if current.is_some(): await current.value.dispose_async() current, is_stopped = Nothing, True break return TailCall(current, is_stopped, current_id) await message_loop(Nothing, False, 0) inner_agent = MailboxProcessor.start(worker) async def asend(xs: AsyncObservable[TSource]) -> None: pipe( InnerObservableMsg(xs), inner_agent.post, ) async def athrow(error: Exception) -> None: await safe_obv.athrow(error) async def aclose() -> None: inner_agent.post(CompletedMsg) _obv = AsyncAnonymousObserver(asend, athrow, aclose) dispose = await pipe( _obv, AsyncObserver, source.subscribe_async, auto_detach, ) async def cancel() -> None: await dispose.dispose_async() inner_agent.post(DisposeMsg) return AsyncDisposable.create(cancel)
async def subscribe_async(_: AsyncObserver[Any]) -> AsyncDisposable: return AsyncDisposable.empty()
async def subscribe_async(aobv: AsyncObserver[Any]) -> AsyncDisposable: await aobv.aclose() return AsyncDisposable.empty()
async def subscribe_async(aobv: AsyncObserver[TSource]) -> AsyncDisposable: safe_obv = safe_observer(aobv, AsyncDisposable.empty()) await safe_obv.asend(value) await safe_obv.aclose() return AsyncDisposable.empty()
def _( source: AsyncObservable[AsyncObservable[TSource]] ) -> AsyncObservable[TSource]: async def subscribe_async( aobv: AsyncObserver[TSource]) -> AsyncDisposable: safe_obv, auto_detach = auto_detach_observer(aobv) initial_model = Model( subscriptions=map.empty, queue=frozenlist.empty, is_stopped=False, key=Key(0), ) async def worker(inbox: MailboxProcessor[Msg[TSource]]) -> None: def obv(key: Key) -> AsyncObserver[TSource]: async def asend(value: TSource) -> None: await safe_obv.asend(value) async def athrow(error: Exception) -> None: await safe_obv.athrow(error) async def aclose() -> None: inbox.post(InnerCompletedMsg(key)) return AsyncAnonymousObserver(asend, athrow, aclose) async def update(msg: Msg[TSource], model: Model[TSource]) -> Model[TSource]: # log.debug("update: %s, model: %s", msg, model) with match(msg) as case: for xs in case(InnerObservableMsg[TSource]): if max_concurrent == 0 or len( model.subscriptions) < max_concurrent: inner = await xs.subscribe_async(obv(model.key) ) return model.replace( subscriptions=model.subscriptions.add( model.key, inner), key=Key(model.key + 1), ) lst = FrozenList.singleton(xs) return model.replace(queue=model.queue.append(lst)) for key in case(InnerCompletedMsg[Key]): subscriptions = model.subscriptions.remove(key) if len(model.queue): xs = model.queue[0] inner = await xs.subscribe_async(obv(model.key) ) return model.replace( subscriptions=subscriptions.add( model.key, inner), key=model.key + 1, queue=model.queue.tail(), ) elif len(subscriptions): return model.replace( subscriptions=subscriptions) else: if model.is_stopped: await safe_obv.aclose() return model.replace(subscriptions=map.empty) while case(CompletedMsg): if not model.subscriptions: log.debug("merge_inner: closing!") await safe_obv.aclose() return model.replace(is_stopped=True) while case.default(): for dispose in model.subscriptions.values(): await dispose.dispose_async() return initial_model.replace(is_stopped=True) async def message_loop(model: Model[TSource]) -> None: while True: msg = await inbox.receive() model = await update(msg, model) if model.is_stopped and not model.subscriptions: break await message_loop(initial_model) agent = MailboxProcessor.start(worker) async def asend(xs: AsyncObservable[TSource]) -> None: log.debug("merge_inner:asend(%s)", xs) agent.post(InnerObservableMsg(inner_observable=xs)) async def athrow(error: Exception) -> None: await safe_obv.athrow(error) agent.post(DisposeMsg) async def aclose() -> None: agent.post(CompletedMsg) obv = AsyncAnonymousObserver(asend, athrow, aclose) dispose = await auto_detach(source.subscribe_async(obv)) async def cancel() -> None: await dispose.dispose_async() agent.post(DisposeMsg) return AsyncDisposable.create(cancel)
def _with_latest_from( source: AsyncObservable[TSource] ) -> AsyncObservable[Tuple[TSource, TOther]]: async def subscribe_async( aobv: AsyncObserver[Tuple[TSource, TOther]]) -> AsyncDisposable: safe_obv, auto_detach = auto_detach_observer(aobv) async def worker(inbox: MailboxProcessor[Msg[TSource]]) -> None: @tailrec_async async def message_loop( latest: Option[TOther]) -> TailCallResult[NoReturn]: cn = await inbox.receive() async def get_value(n: Notification[Any]) -> Option[Any]: with match(n) as case: for value in case(OnNext[TSource]): return Some(value) for err in case(OnError[TSource]): await safe_obv.athrow(err) while case.default(): await safe_obv.aclose() return Nothing source_value = Nothing if isinstance(cn, SourceMsg): cn = cast(SourceMsg[TSource], cn) source_value = await get_value(cn.value) else: cn = cast(OtherMsg[TOther], cn) latest = await get_value(cn.value) def binder(s: TSource) -> Option[Tuple[TSource, TOther]]: def mapper(o: TOther) -> Tuple[TSource, TOther]: return (s, o) return latest.map(mapper) combined = source_value.bind(binder) for x in combined.to_list(): await safe_obv.asend(x) return TailCall(latest) await message_loop(Nothing) agent = MailboxProcessor.start(worker) async def obv_fn1(n: Notification[TSource]) -> None: pipe(SourceMsg(n), agent.post) async def obv_fn2(n: Notification[TOther]) -> None: pipe(OtherMsg(n), agent.post) obv1: AsyncObserver[TSource] = AsyncNotificationObserver(obv_fn1) obv2: AsyncObserver[TOther] = AsyncNotificationObserver(obv_fn2) dispose1 = await pipe(obv1, source.subscribe_async, auto_detach) dispose2 = await pipe(obv2, other.subscribe_async, auto_detach) return AsyncDisposable.composite(dispose1, dispose2)