diff --git a/random-number-range/.gitignore b/random-number-range/.gitignore new file mode 100644 index 00000000..eab0d4b0 --- /dev/null +++ b/random-number-range/.gitignore @@ -0,0 +1,4 @@ +*.db +*.py[cod] +.web +__pycache__/ \ No newline at end of file diff --git a/random-number-range/assets/favicon.ico b/random-number-range/assets/favicon.ico new file mode 100644 index 00000000..609f6abc Binary files /dev/null and b/random-number-range/assets/favicon.ico differ diff --git a/random-number-range/random_number_range/__init__.py b/random-number-range/random_number_range/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/random-number-range/random_number_range/random_number_range.py b/random-number-range/random_number_range/random_number_range.py new file mode 100644 index 00000000..e8695107 --- /dev/null +++ b/random-number-range/random_number_range/random_number_range.py @@ -0,0 +1,148 @@ +"""This example demonstrates two techniques for achieving a long running task: + +Chained Events: each step of the event recursively queues itself to run again. +Background Tasks: a background task is started and runs until it is cancelled. + +The background task is the newer approach and is generally preferrable because +it does not block UI interaction while it is running. +""" +import asyncio +import random +from typing import List + +import reflex as rx + + +class BaseState(rx.State): + rrange: List[int] = [-10, 10] + delay: int = 1 + _last_values: List[int] = [] + total: int = 0 + + @rx.var + def last_values(self) -> str: + return ", ".join(str(x) for x in self._last_values[-10:]) + + def balance(self): + max_magnitude = max(abs(x) for x in self.rrange) + self.rrange = [-max_magnitude, max_magnitude] + + +class BackgroundState(BaseState): + running: bool = False + loading: bool = False + _n_tasks: int = 0 + + @rx.background + async def run_task(self): + async with self: + if self._n_tasks > 0: + return + self._n_tasks += 1 + + while True: + async with self: + self.loading = True + + # Simulate long running API call + await asyncio.sleep(self.delay) + + async with self: + last_value = random.randint(*self.rrange) + self.total += last_value + self._last_values = self._last_values[-9:] + [last_value] + self.loading = False + if not self.running: + break + + async with self: + self._n_tasks -= 1 + + def set_running(self, value: bool): + self.running = value + if value: + return BackgroundState.run_task + + def single_step(self): + self.running = False + return BackgroundState.run_task + + +class ChainState(BaseState): + running: bool = False + loading: bool = False + + async def run_task(self): + self.loading = True + yield + + # Simulate long running API call + await asyncio.sleep(self.delay) + + last_value = random.randint(*self.rrange) + self.total += last_value + self._last_values = self._last_values[-9:] + [last_value] + self.loading = False + + if self.running: + yield ChainState.run_task + + def set_running(self, value: bool): + self.running = value + if self.running: + return ChainState.run_task + + def single_step(self): + self.running = False + return ChainState.run_task + + +other_links = { + "Chain Events": lambda State: rx.link("Background Task Version", href="/background", on_click=State.set_running(False)), + "Background Task": lambda State: rx.link("Chain Event Version", href="/chain", on_click=State.set_running(False)), +} + + +def random_numbers_in_range(State, mode: str) -> rx.Component: + return rx.center( + rx.vstack( + rx.heading(f"Random Numbers in Range"), + rx.heading(f"{mode} version", font_size="1.5em"), + other_links[mode](State), + rx.hstack( + rx.text("Min: ", State.rrange[0], padding_right="3em"), + rx.button("Balance", on_click=State.balance), + rx.text("Max: ", State.rrange[1], padding_left="3em"), + ), + rx.range_slider(value=State.rrange, on_change=State.set_rrange, min_=-100, max_=100), + rx.hstack( + rx.text("Last 10 values: ", State.last_values), + rx.cond(State.loading, rx.spinner()), + ), + rx.hstack( + rx.text("Total: ", State.total), + rx.button("Clear", on_click=lambda: State.set_total(0)), + ), + rx.hstack( + rx.vstack( + rx.text("Run", font_size="0.7em"), + rx.switch(is_checked=State.running, on_change=State.set_running), + ), + rx.vstack( + rx.text("Delay (sec)", font_size="0.7em"), + rx.select(*[rx.option(x) for x in range(1, 5)], value=State.delay.to(str), on_change=State.set_delay), + padding_right="3em", + ), + rx.button("Single Step", on_click=State.single_step), + align_items="flex-start", + ), + width="50vw", + ), + ) + + +app = rx.App() +app.add_page(rx.fragment(on_mount=rx.redirect("/chain")), route="/") +app.add_page(random_numbers_in_range(ChainState, "Chain Events"), route="/chain") +app.add_page(random_numbers_in_range(BackgroundState, "Background Task"), route="/background") +app.compile() diff --git a/random-number-range/requirements.txt b/random-number-range/requirements.txt new file mode 100644 index 00000000..21e07bd4 --- /dev/null +++ b/random-number-range/requirements.txt @@ -0,0 +1 @@ +reflex>=0.2.8 diff --git a/random-number-range/rxconfig.py b/random-number-range/rxconfig.py new file mode 100644 index 00000000..b36d140e --- /dev/null +++ b/random-number-range/rxconfig.py @@ -0,0 +1,5 @@ +import reflex as rx + +config = rx.Config( + app_name="random_number_range", +) \ No newline at end of file