Example #1
0
def handle_websocket():

    wsock = request.environ.get("wsgi.websocket")
    if not wsock:
        abort(400, "Expected WebSocket request.")
    stream = Subject()
    query = (
        stream.map(lambda x: x["term"]).filter(lambda text: len(
            text) > 2)  # Only if text is longer than 2 characters
        .debounce(0.750, scheduler=scheduler)  # Pause for 750ms
        .distinct_until_changed())  # Only if the value has changed

    searcher = query.flat_map_latest(lambda term: WikiFinder(term))

    def send_response(x):
        wsock.on_next(x)

    def on_error(ex):
        print(ex)

    searcher.subscribe(send_response, on_error)

    while True:
        try:
            message = wsock.receive()
            # like {'term': '<current textbox val>'}
            obj = json.loads(message)
            stream.on_next(obj)
        except WebSocketError:
            break
Example #2
0
    def __init__(self):
        QWidget.__init__(self)
        self.setWindowTitle("Rx for Python rocks")
        self.resize(600, 600)
        self.setMouseTracking(True)

        # This Subject is used to transmit mouse moves to labels
        self.mousemove = Subject()
Example #3
0
    def __init__(self):
        super().__init__()
        self.resize(600, 600)

        self.add_events(Gdk.EventMask.POINTER_MOTION_MASK)
        self.connect("motion-notify-event", self.on_mouse_move)

        self.mousemove = Subject()
Example #4
0
    def __init__(self):
        super(Frame, self).__init__(None)
        self.SetTitle("Rx for Python rocks")
        self.SetSize((600, 600))

        # This Subject is used to transmit mouse moves to labels
        self.mousemove = Subject()

        self.Bind(wx.EVT_MOTION, self.OnMotion)
Example #5
0
 def __init__(self) -> None:
     self._subject = Subject()
     self._scheduler = ThreadPoolScheduler(max_workers=1)
     obs = self._subject.pipe(ops.observe_on(self._scheduler))
     self._disposable = obs \
         .pipe(ops.window_with_time_or_count(count=5, timespan=datetime.timedelta(milliseconds=10_000)),
               ops.flat_map(lambda x: self._window_to_group(x)),
               ops.map(mapper=lambda x: self._retryable(data=x, delay=self._jitter_delay(jitter_interval=1000))),
               ops.merge_all()) \
         .subscribe(self._result, self._error, self._on_complete)
     pass
Example #6
0
class Window(QWidget):
    def __init__(self):
        QWidget.__init__(self)
        self.setWindowTitle("Rx for Python rocks")
        self.resize(600, 600)
        self.setMouseTracking(True)

        # This Subject is used to transmit mouse moves to labels
        self.mousemove = Subject()

    def mouseMoveEvent(self, event):
        self.mousemove.on_next((event.x(), event.y()))
Example #7
0
class Window(Gtk.Window):
    def __init__(self):
        super().__init__()
        self.resize(600, 600)

        self.add_events(Gdk.EventMask.POINTER_MOTION_MASK)
        self.connect("motion-notify-event", self.on_mouse_move)

        self.mousemove = Subject()

    def on_mouse_move(self, widget, event):
        self.mousemove.on_next((event.x, event.y))
Example #8
0
                def action(scheduler: abc.SchedulerBase, state: Any = None):
                    s: Optional[Subject[_T]] = None

                    if is_shift:
                        s = Subject()
                        queue.append(s)
                        observer.on_next(add_ref(s, ref_count_disposable))

                    if is_span:
                        s = queue.pop(0)
                        s.on_completed()

                    create_timer()
Example #9
0
class Frame(wx.Frame):
    def __init__(self):
        super(Frame, self).__init__(None)
        self.SetTitle("Rx for Python rocks")
        self.SetSize((600, 600))

        # This Subject is used to transmit mouse moves to labels
        self.mousemove = Subject()

        self.Bind(wx.EVT_MOTION, self.OnMotion)

    def OnMotion(self, event):
        self.mousemove.on_next((event.GetX(), event.GetY()))
Example #10
0
def publish_(
    mapper: Optional[Mapper[Observable[_TSource], Observable[_TResult]]] = None,
) -> Callable[
    [Observable[_TSource]], Union[Observable[_TResult], ConnectableObservable[_TSource]]
]:
    """Returns an observable sequence that is the result of invoking the
    mapper on a connectable observable sequence that shares a single
    subscription to the underlying sequence. This operator is a
    specialization of Multicast using a regular Subject.

    Example:
        >>> res = publish()
        >>> res = publish(lambda x: x)

    mapper: [Optional] Selector function which can use the
        multicasted source sequence as many times as needed, without causing
        multiple subscriptions to the source sequence. Subscribers to the
        given source will receive all notifications of the source from the
        time of the subscription on.

    Returns:
        An observable sequence that contains the elements of a sequence
        produced by multicasting the source sequence within a mapper
        function.
    """

    if mapper:

        def factory(scheduler: Optional[abc.SchedulerBase] = None) -> Subject[_TSource]:
            return Subject()

        return ops.multicast(subject_factory=factory, mapper=mapper)

    subject: Subject[_TSource] = Subject()
    return ops.multicast(subject=subject)
Example #11
0
    def open(self):
        scheduler = AsyncIOScheduler(asyncio.get_event_loop())

        print("WebSocket opened")

        # A Subject is both an observable and observer, so we can both subscribe
        # to it and also feed (send) it with new values
        self.subject: Subject[Dict[str, str]] = Subject()

        # Get all distinct key up events from the input and only fire if long enough and distinct
        searcher = self.subject.pipe(
            ops.map(lambda x: x["term"]),
            ops.filter(lambda text: len(text) > 2
                       ),  # Only if the text is longer than 2 characters
            ops.debounce(0.750),  # Pause for 750ms
            ops.distinct_until_changed(),  # Only if the value has changed
            ops.flat_map_latest(search_wikipedia),
        )

        def send_response(x: HTTPResponse) -> None:
            self.write_message(x.body)

        def on_error(ex: Exception):
            print(ex)

        searcher.subscribe(on_next=send_response,
                           on_error=on_error,
                           scheduler=scheduler)
Example #12
0
 async def test_flat_map():
     x: Subject[int] = Subject()
     x.pipe(ops.flat_map(mapper)).subscribe(on_next,
                                            on_error,
                                            scheduler=scheduler)
     x.on_next(10)
     await asyncio.sleep(0.1)
Example #13
0
def main() -> None:
    root = Tk()
    root.title("Rx for Python rocks")
    scheduler = TkinterScheduler(root)

    mousemoves: Subject[Event[Any]] = Subject()

    frame = Frame(root, width=600, height=600)
    frame.bind("<Motion>", mousemoves.on_next)

    text = "TIME FLIES LIKE AN ARROW"

    def on_next(info: Tuple[tkinter.Label, "Event[Frame]", int]) -> None:
        label, ev, i = info
        label.place(x=ev.x + i * 12 + 15, y=ev.y)

    def label2stream(
            label: tkinter.Label, index: int
    ) -> Observable[Tuple[tkinter.Label, "Event[Frame]", int]]:

        return mousemoves.pipe(
            ops.map(lambda ev: (label, ev, index)),
            ops.delay(index * 0.1),
        )

    def char2label(char: str) -> Label:
        return Label(frame, text=char, borderwidth=0, padx=0, pady=0)

    reactivex.from_(text).pipe(
        ops.map(char2label),
        ops.flat_map_indexed(label2stream),
    ).subscribe(on_next, on_error=print, scheduler=scheduler)

    frame.pack()
    root.mainloop()
Example #14
0
    def test_multicast_hot_4(self):
        c = [None]
        d1 = [None]
        d2 = [None]
        ex = "ex"
        scheduler = TestScheduler()
        xs = scheduler.create_hot_observable(
            on_next(40, 0),
            on_next(90, 1),
            on_next(150, 2),
            on_next(210, 3),
            on_next(240, 4),
            on_next(270, 5),
            on_next(330, 6),
            on_next(340, 7),
            on_error(390, ex),
        )
        s = Subject()
        o = scheduler.create_observer()

        def action0(scheduler: abc.SchedulerBase, state: Any = None):
            c[0] = xs.pipe(ops.multicast(s))

        scheduler.schedule_absolute(50, action0)

        def action1(scheduler: abc.SchedulerBase, state: Any = None):
            d2[0] = c[0].connect(scheduler)

        scheduler.schedule_absolute(100, action1)

        def action2(scheduler: abc.SchedulerBase, state: Any = None):
            d1[0] = c[0].subscribe(o, scheduler)

        scheduler.schedule_absolute(200, action2)

        def action3(scheduler: abc.SchedulerBase, state: Any = None):
            d2[0].dispose()

        scheduler.schedule_absolute(300, action3)

        def action4(scheduler: abc.SchedulerBase, state: Any = None):
            d2[0] = c[0].connect(scheduler)

        scheduler.schedule_absolute(335, action4)

        scheduler.start()
        assert o.messages == [
            on_next(210, 3),
            on_next(240, 4),
            on_next(270, 5),
            on_next(340, 7),
            on_error(390, ex),
        ]
        assert xs.subscriptions == [subscribe(100, 300), subscribe(335, 390)]
Example #15
0
                def action(scheduler: abc.SchedulerBase, state: Any = None):
                    nonlocal n, s, window_id
                    if _id != window_id:
                        return

                    n = 0
                    window_id += 1
                    new_id = window_id
                    s.on_completed()
                    s = Subject()
                    observer.on_next(add_ref(s, ref_count_disposable))
                    create_timer(new_id)
Example #16
0
            def on_next_left(value: _TLeft) -> None:
                subject: Subject[_TRight] = Subject()

                with left.lock:
                    _id = left_id[0]
                    left_id[0] += 1
                    left_map[_id] = subject

                try:
                    result = (value, add_ref(subject, rcd))
                except Exception as e:
                    log.error("*** Exception: %s" % e)
                    for left_value in left_map.values():
                        left_value.on_error(e)

                    observer.on_error(e)
                    return

                observer.on_next(result)

                for right_value in right_map.values():
                    subject.on_next(right_value)

                md = SingleAssignmentDisposable()
                group.add(md)

                def expire():
                    if _id in left_map:
                        del left_map[_id]
                        subject.on_completed()

                    group.remove(md)

                try:
                    duration = left_duration_mapper(value)
                except Exception as e:
                    for left_value in left_map.values():
                        left_value.on_error(e)

                    observer.on_error(e)
                    return

                def on_error(error: Exception) -> Any:
                    for left_value in left_map.values():
                        left_value.on_error(error)

                    observer.on_error(error)

                md.disposable = duration.pipe(ops.take(1)).subscribe(
                    nothing, on_error, expire, scheduler=scheduler)
Example #17
0
    def test_connectable_observable_creation(self):
        y = [0]

        s2 = Subject()
        co2 = ConnectableObservable(reactivex.return_value(1), s2)

        def on_next(x):
            y[0] = x

        co2.subscribe(on_next=on_next)
        self.assertNotEqual(1, y[0])

        co2.connect()
        self.assertEqual(1, y[0])
Example #18
0
    def test_multicast_hot_1(self):
        scheduler = TestScheduler()

        s: Subject[int] = Subject()

        xs = scheduler.create_hot_observable(
            on_next(40, 0),
            on_next(90, 1),
            on_next(150, 2),
            on_next(210, 3),
            on_next(240, 4),
            on_next(270, 5),
            on_next(330, 6),
            on_next(340, 7),
            on_completed(390),
        )

        obv = scheduler.create_observer()
        d1: List[Optional[abc.DisposableBase]] = [None]
        d2: List[Optional[abc.DisposableBase]] = [None]
        c: List[Optional[ConnectableObservable[int]]] = [None]

        def action(scheduler: abc.SchedulerBase, state: Any = None):
            c[0] = xs.pipe(ops.multicast(s))

        scheduler.schedule_absolute(50, action)

        def action0(scheduler: abc.SchedulerBase, state: Any = None):
            assert c[0]
            d1[0] = c[0].subscribe(obv, scheduler=scheduler)

        scheduler.schedule_absolute(100, action0)

        def action1(scheduler: abc.SchedulerBase, state: Any = None):
            assert c[0]
            d2[0] = c[0].connect(scheduler)

        scheduler.schedule_absolute(200, action1)

        def action2(scheduler: abc.SchedulerBase, state: Any = None):
            assert d1[0]
            d1[0].dispose()

        scheduler.schedule_absolute(300, action2)

        scheduler.start()

        assert obv.messages == [on_next(210, 3), on_next(240, 4), on_next(270, 5)]
        assert xs.subscriptions == [subscribe(200, 390)]
Example #19
0
        def subscribe(
            observer: abc.ObserverBase[Observable[_T]],
            scheduler: Optional[abc.SchedulerBase] = None,
        ):
            m = SerialDisposable()
            d = CompositeDisposable(m)
            r = RefCountDisposable(d)
            window: Subject[_T] = Subject()

            observer.on_next(add_ref(window, r))

            def on_next(value: _T) -> None:
                window.on_next(value)

            def on_error(error: Exception) -> None:
                window.on_error(error)
                observer.on_error(error)

            def on_completed() -> None:
                window.on_completed()
                observer.on_completed()

            d.add(
                source.subscribe(on_next,
                                 on_error,
                                 on_completed,
                                 scheduler=scheduler))

            def create_window_on_completed():
                try:
                    window_close = closing_mapper()
                except Exception as exception:
                    observer.on_error(exception)
                    return

                def on_completed():
                    nonlocal window
                    window.on_completed()
                    window = Subject()
                    observer.on_next(add_ref(window, r))
                    create_window_on_completed()

                m1 = SingleAssignmentDisposable()
                m.disposable = m1
                m1.disposable = window_close.pipe(ops.take(1)).subscribe(
                    noop, on_error, on_completed, scheduler=scheduler)

            create_window_on_completed()
            return r
Example #20
0
    def test_dispose_on_cancel(self):
        loop = asyncio.get_event_loop()
        sub = Subject()

        async def using_sub():
            # Since the subject never completes, this await statement
            # will never be complete either. We wait forever.
            await reactivex.using(lambda: sub, lambda s: s)

        async def go():
            await asyncio.wait_for(using_sub(), 0.1)

        self.assertRaises(asyncio.TimeoutError, loop.run_until_complete, go())
        # When we cancel the future (due to the time-out), the future
        # automatically disposes the underlying subject.
        self.assertTrue(sub.is_disposed)
Example #21
0
            def on_next(x: _T) -> None:
                nonlocal n, s, window_id
                new_window = False
                new_id = 0

                s.on_next(x)
                n += 1
                if n == count:
                    new_window = True
                    n = 0
                    window_id += 1
                    new_id = window_id
                    s.on_completed()
                    s = Subject()
                    observer.on_next(add_ref(s, ref_count_disposable))

                if new_window:
                    create_timer(new_id)
Example #22
0
    def open(self):
        print("WebSocket opened")

        # A Subject is both an observable and observer, so we can both subscribe
        # to it and also feed (on_next) it with new values
        self.subject: Subject[Dict[str, int]] = Subject()

        # Now we take on our magic glasses and project the stream of bytes into
        # a ...
        query = self.subject.pipe(
            # 1. stream of keycodes
            ops.map(lambda obj: obj["keycode"]),
            # 2. stream of windows (10 ints long)
            ops.window_with_count(10, 1),
            # 3. stream of booleans, True or False
            ops.flat_map(lambda win: win.pipe(ops.sequence_equal(codes))),
            # 4. stream of Trues
            ops.filter(lambda equal: equal),
        )
        # 4. we then subscribe to the Trues, and signal Konami! if we see any
        query.subscribe(on_next=lambda x: self.write_message("Konami!"))
Example #23
0
        def subscribe(
            observer: abc.ObserverBase[Observable[_T]],
            scheduler: Optional[abc.SchedulerBase] = None,
        ) -> abc.DisposableBase:
            window_subject: Subject[_T] = Subject()
            d = CompositeDisposable()
            r = RefCountDisposable(d)

            observer.on_next(add_ref(window_subject, r))

            def on_next_window(x: _T) -> None:
                window_subject.on_next(x)

            def on_error(err: Exception) -> None:
                window_subject.on_error(err)
                observer.on_error(err)

            def on_completed() -> None:
                window_subject.on_completed()
                observer.on_completed()

            d.add(
                source.subscribe(on_next_window,
                                 on_error,
                                 on_completed,
                                 scheduler=scheduler))

            def on_next_observer(w: Observable[_T]):
                nonlocal window_subject
                window_subject.on_completed()
                window_subject = Subject()
                observer.on_next(add_ref(window_subject, r))

            d.add(
                boundaries.subscribe(on_next_observer,
                                     on_error,
                                     on_completed,
                                     scheduler=scheduler))
            return r
Example #24
0
class _RxWriter(object):
    success_count = 0
    failed_count = 0
    raise_retry_exception = 0

    def __init__(self) -> None:
        self._subject = Subject()
        self._scheduler = ThreadPoolScheduler(max_workers=1)
        obs = self._subject.pipe(ops.observe_on(self._scheduler))
        self._disposable = obs \
            .pipe(ops.window_with_time_or_count(count=5, timespan=datetime.timedelta(milliseconds=10_000)),
                  ops.flat_map(lambda x: self._window_to_group(x)),
                  ops.map(mapper=lambda x: self._retryable(data=x, delay=self._jitter_delay(jitter_interval=1000))),
                  ops.merge_all()) \
            .subscribe(self._result, self._error, self._on_complete)
        pass

    def close(self):
        self.__del__()

    def __del__(self):
        if self._subject:
            self._subject.on_completed()
            self._subject.dispose()
            self._subject = None

            while not self._disposable.is_disposed:
                time.sleep(0.1)

        if self._disposable:
            self._disposable = None
        pass

    def _window_to_group(self, value):
        return value.pipe(
            ops.to_iterable(),
            ops.map(
                lambda x: rx.from_iterable(x).pipe(ops.group_by(
                    _group_by), ops.map(_group_to_batch), ops.merge_all())),
            ops.merge_all())

    def _retryable(self, data: str, delay: datetime.timedelta):

        return rx.of(data).pipe(
            ops.delay(duetime=delay, scheduler=self._scheduler),
            ops.map(lambda x: self._http(x)),
            ops.catch(handler=lambda exception, source: self._retry_handler(
                exception, source, data)),
        )

    def _http(self, data: str):
        if "gamma" in data:
            print('bad request[{}]: {}'.format(current_thread().name, data))
            raise Exception('unexpected token: {}'.format(data))
            pass

        if "alpha" in data:
            if self.raise_retry_exception < 2:
                self.raise_retry_exception += 1
                print(
                    'server is temporarily unavailable to accept writes[{}]: {}'
                    .format(current_thread().name, data))
                raise Exception(
                    'server is temporarily unavailable to accept writes: {}'.
                    format(data))
            else:
                print("server is OK: {}".format(datetime.datetime.now()))
            pass

        print("http[" + current_thread().name + "]: " + data)
        return _Notification(data=data)

    def write(self, data: str):
        print("write[" + current_thread().name + "]")
        self._subject.on_next(data)
        pass

    def _result(self, data: _Notification):
        print("result[" + current_thread().name + "]: " + str(data))
        if data.exception:
            self.failed_count += 1
        else:
            self.success_count += 1
        pass

    def _error(self, error):
        print(error)

    def _on_complete(self):
        self._disposable.dispose()
        print("on complete")

    def _jitter_delay(self, jitter_interval=0):
        _jitter = datetime.timedelta(milliseconds=random() * jitter_interval)
        print('jitter: {}'.format(_jitter))
        return _jitter

    def _retry_handler(self, exception, source, data):
        print('retry_handler: {}, source: {}'.format(exception, source))

        if "server is temporarily" in str(exception):
            print("RETRY!!!: {}".format(datetime.datetime.now()))
            return self._retryable(data, delay=datetime.timedelta(seconds=2))

        notification = _Notification(exception=exception, data=data)

        return rx.just(notification)
Example #25
0
    def test_connectable_observable_multiple_non_overlapped_connections(self):
        scheduler = TestScheduler()

        xs = scheduler.create_hot_observable(
            on_next(210, 1),
            on_next(220, 2),
            on_next(230, 3),
            on_next(240, 4),
            on_next(250, 5),
            on_next(260, 6),
            on_next(270, 7),
            on_next(280, 8),
            on_next(290, 9),
            on_completed(300),
        )

        subject = Subject()

        conn = xs.pipe(ops.multicast(subject))

        c1 = [None]

        def action10(scheduler, state):
            c1[0] = conn.connect(scheduler)

        scheduler.schedule_absolute(225, action10)

        def action11(scheduler, state):
            c1[0].dispose()

        scheduler.schedule_absolute(241, action11)

        def action12(scheduler, state):
            c1[0].dispose()  # idempotency test

        scheduler.schedule_absolute(245, action12)

        def action13(scheduler, state):
            c1[0].dispose()  # idempotency test

        scheduler.schedule_absolute(251, action13)

        def action14(scheduler, state):
            c1[0].dispose()  # idempotency test

        scheduler.schedule_absolute(260, action14)

        c2 = [None]

        def action20(scheduler, state):
            c2[0] = conn.connect(scheduler)

        scheduler.schedule_absolute(249, action20)

        def action21(scheduler, state):
            c2[0].dispose()

        scheduler.schedule_absolute(255, action21)

        def action22(scheduler, state):
            c2[0].dispose()  # idempotency test

        scheduler.schedule_absolute(265, action22)

        def action23(scheduler, state):
            c2[0].dispose()  # idempotency test

        scheduler.schedule_absolute(280, action23)

        c3 = [None]

        def action30(scheduler, state):
            c3[0] = conn.connect(scheduler)

        scheduler.schedule_absolute(275, action30)

        def action31(scheduler, state):
            c3[0].dispose()

        scheduler.schedule_absolute(295, action31)

        res = scheduler.start(lambda: conn)

        assert res.messages == [
            on_next(230, 3),
            on_next(240, 4),
            on_next(250, 5),
            on_next(280, 8),
            on_next(290, 9),
        ]

        assert xs.subscriptions == [
            subscribe(225, 241),
            subscribe(249, 255),
            subscribe(275, 295),
        ]
Example #26
0
def main():
    pygame.init()

    size = 500, 500
    screen = pygame.display.set_mode(size)
    pygame.display.set_caption("Rx for Python rocks")

    black = 0, 0, 0
    background = pygame.Surface(screen.get_size())
    background.fill(black)  # fill the background black
    background = background.convert()  # prepare for faster blitting

    scheduler = PyGameScheduler(pygame)

    mousemove = Subject()

    color = "white"
    base = dirname(__file__)
    files = [
        join(base, img % color) for img in [
            "chess_rook_%s.png",
            "chess_knight_%s.png",
            "chess_bishop_%s.png",
            "chess_king_%s.png",
            "chess_queen_%s.png",
            "chess_bishop_%s.png",
            "chess_knight_%s.png",
            "chess_rook_%s.png",
        ]
    ]
    images = [pygame.image.load(image).convert_alpha() for image in files]

    old = [None] * len(images)
    draw = []
    erase = []

    def handle_image(i, image):
        imagerect = image.get_rect()

        def on_next(ev):
            imagerect.top = ev[1]
            imagerect.left = ev[0] + i * 32

            if old[i]:
                erase.append(old[i])
            old[i] = imagerect.copy()
            draw.append((image, imagerect.copy()))

        def on_error(err):
            print("Got error: %s" % err)
            sys.exit()

        mousemove.pipe(ops.delay(0.1 * i, scheduler=scheduler)).subscribe(
            on_next, on_error=on_error)

    for i, image in enumerate(images):
        handle_image(i, image)

    while True:
        for event in pygame.event.get():
            if event.type == pygame.MOUSEMOTION:
                pos = event.pos
                mousemove.on_next(pos)
            elif event.type == pygame.QUIT:
                sys.exit()

        if len(draw):
            update = []
            for rect in erase:
                screen.blit(background, (rect.x, rect.y), rect)
                update.append(rect)

            for image, rect in draw:
                screen.blit(image, rect)
                update.append(rect)

            pygame.display.update(update)
            pygame.display.flip()
            draw = []
            erase = []

        scheduler.run()
Example #27
0
 def action1(scheduler, state=None):
     s[0] = Subject()
Example #28
0
def group_by_until_(
    key_mapper: Mapper[_T, _TKey],
    element_mapper: Optional[Mapper[_T, _TValue]],
    duration_mapper: Callable[[GroupedObservable[_TKey, _TValue]],
                              Observable[Any]],
    subject_mapper: Optional[Callable[[], Subject[_TValue]]] = None,
) -> Callable[[Observable[_T]], Observable[GroupedObservable[_TKey, _TValue]]]:
    """Groups the elements of an observable sequence according to a
    specified key mapper function. A duration mapper function is used
    to control the lifetime of groups. When a group expires, it receives
    an OnCompleted notification. When a new element with the same key
    value as a reclaimed group occurs, the group will be reborn with a
    new lifetime request.

    Examples:
        >>> group_by_until(lambda x: x.id, None, lambda : reactivex.never())
        >>> group_by_until(
            lambda x: x.id,lambda x: x.name, lambda grp: reactivex.never()
        )
        >>> group_by_until(
            lambda x: x.id,
            lambda x: x.name,
            lambda grp: reactivex.never(),
            lambda: ReplaySubject()
        )

    Args:
        key_mapper: A function to extract the key for each element.
        duration_mapper: A function to signal the expiration of a group.
        subject_mapper: A function that returns a subject used to initiate
            a grouped observable. Default mapper returns a Subject object.

    Returns: a sequence of observable groups, each of which corresponds to
    a unique key value, containing all elements that share that same key
    value. If a group's lifetime expires, a new group with the same key
    value can be created once an element with such a key value is
    encountered.
    """

    element_mapper_ = element_mapper or cast(Mapper[_T, _TValue], identity)

    default_subject_mapper: Callable[[], Subject[_TValue]] = lambda: Subject()
    subject_mapper_ = subject_mapper or default_subject_mapper

    def group_by_until(
        source: Observable[_T],
    ) -> Observable[GroupedObservable[_TKey, _TValue]]:
        def subscribe(
            observer: abc.ObserverBase[GroupedObservable[_TKey, _TValue]],
            scheduler: Optional[abc.SchedulerBase] = None,
        ) -> abc.DisposableBase:
            writers: OrderedDict[_TKey, Subject[_TValue]] = OrderedDict()
            group_disposable = CompositeDisposable()
            ref_count_disposable = RefCountDisposable(group_disposable)

            def on_next(x: _T) -> None:
                writer = None
                key = None

                try:
                    key = key_mapper(x)
                except Exception as e:
                    for wrt in writers.values():
                        wrt.on_error(e)

                    observer.on_error(e)
                    return

                fire_new_map_entry = False
                writer = writers.get(key)
                if not writer:
                    try:
                        writer = subject_mapper_()
                    except Exception as e:
                        for wrt in writers.values():
                            wrt.on_error(e)

                        observer.on_error(e)
                        return

                    writers[key] = writer
                    fire_new_map_entry = True

                if fire_new_map_entry:
                    group: GroupedObservable[_TKey,
                                             _TValue] = GroupedObservable(
                                                 key, writer,
                                                 ref_count_disposable)
                    duration_group: GroupedObservable[_TKey,
                                                      Any] = GroupedObservable(
                                                          key, writer)
                    try:
                        duration = duration_mapper(duration_group)
                    except Exception as e:
                        for wrt in writers.values():
                            wrt.on_error(e)

                        observer.on_error(e)
                        return

                    observer.on_next(group)
                    sad = SingleAssignmentDisposable()
                    group_disposable.add(sad)

                    def expire():
                        if writers[key]:
                            del writers[key]
                            writer.on_completed()

                        group_disposable.remove(sad)

                    def on_next(value: Any) -> None:
                        pass

                    def on_error(exn: Exception) -> None:
                        for wrt in writers.values():
                            wrt.on_error(exn)
                        observer.on_error(exn)

                    def on_completed():
                        expire()

                    sad.disposable = duration.pipe(ops.take(1)).subscribe(
                        on_next, on_error, on_completed, scheduler=scheduler)

                try:
                    element = element_mapper_(x)
                except Exception as error:
                    for wrt in writers.values():
                        wrt.on_error(error)

                    observer.on_error(error)
                    return

                writer.on_next(element)

            def on_error(ex: Exception) -> None:
                for wrt in writers.values():
                    wrt.on_error(ex)

                observer.on_error(ex)

            def on_completed() -> None:
                for wrt in writers.values():
                    wrt.on_completed()

                observer.on_completed()

            group_disposable.add(
                source.subscribe(on_next,
                                 on_error,
                                 on_completed,
                                 scheduler=scheduler))
            return ref_count_disposable

        return Observable(subscribe)

    return group_by_until
Example #29
0
 def subject_factory(scheduler):
     return Subject()
Example #30
0
        def subscribe(
            observer: abc.ObserverBase[Observable[_T]],
            scheduler_: Optional[abc.SchedulerBase] = None,
        ):
            _scheduler = scheduler or scheduler_ or TimeoutScheduler.singleton(
            )

            timer_d = SerialDisposable()
            next_shift = [timeshift]
            next_span = [timespan]
            total_time = [DELTA_ZERO]
            queue: List[Subject[_T]] = []

            group_disposable = CompositeDisposable(timer_d)
            ref_count_disposable = RefCountDisposable(group_disposable)

            def create_timer():
                m = SingleAssignmentDisposable()
                timer_d.disposable = m
                is_span = False
                is_shift = False

                if next_span[0] == next_shift[0]:
                    is_span = True
                    is_shift = True
                elif next_span[0] < next_shift[0]:
                    is_span = True
                else:
                    is_shift = True

                new_total_time = next_span[0] if is_span else next_shift[0]

                ts = new_total_time - total_time[0]
                total_time[0] = new_total_time
                if is_span:
                    next_span[0] += timeshift

                if is_shift:
                    next_shift[0] += timeshift

                @synchronized(source.lock)
                def action(scheduler: abc.SchedulerBase, state: Any = None):
                    s: Optional[Subject[_T]] = None

                    if is_shift:
                        s = Subject()
                        queue.append(s)
                        observer.on_next(add_ref(s, ref_count_disposable))

                    if is_span:
                        s = queue.pop(0)
                        s.on_completed()

                    create_timer()

                m.disposable = _scheduler.schedule_relative(ts, action)

            queue.append(Subject())
            observer.on_next(add_ref(queue[0], ref_count_disposable))
            create_timer()

            def on_next(x: _T) -> None:
                with source.lock:
                    for s in queue:
                        s.on_next(x)

            @synchronized(source.lock)
            def on_error(e: Exception) -> None:
                for s in queue:
                    s.on_error(e)

                observer.on_error(e)

            @synchronized(source.lock)
            def on_completed() -> None:
                for s in queue:
                    s.on_completed()

                observer.on_completed()

            group_disposable.add(
                source.subscribe(on_next,
                                 on_error,
                                 on_completed,
                                 scheduler=scheduler_))
            return ref_count_disposable