From c51767fe9e9e555df714c71142372a8198029840 Mon Sep 17 00:00:00 2001 From: Farhan Ali Raza Date: Tue, 2 Jun 2026 02:33:07 +0500 Subject: [PATCH 1/5] refactor: drop pandas/numpy from nba template Load the roster once at import time instead of re-reading the CSV into per-connection state on every page load, and split the monolithic State into a table-only State and a chart-only StatsState so chart data is never computed or sent to the client while the Table tab is showing. Filtering/aggregation now uses stdlib csv and random plus shared _passes_filters / _average_by helpers, removing the pandas and numpy dependencies entirely. --- nba/nba/backend/backend.py | 397 ++++++++++++--------------- nba/nba/components/item_badges.py | 8 +- nba/nba/components/stats_selector.py | 24 +- nba/nba/nba.py | 2 - nba/nba/views/stats.py | 42 +-- 5 files changed, 197 insertions(+), 276 deletions(-) diff --git a/nba/nba/backend/backend.py b/nba/nba/backend/backend.py index f17b5db..836b5e1 100644 --- a/nba/nba/backend/backend.py +++ b/nba/nba/backend/backend.py @@ -1,42 +1,74 @@ +import csv +import random +from pathlib import Path from typing import Dict, List -import numpy as np -import pandas as pd import reflex as rx from .data_items import all_items from .player import Player -nba_csv = "nbastats.csv" +_CSV_PATH = Path(__file__).resolve().parents[2] / "nbastats.csv" -class State(rx.State): - """The app state.""" +def _load_players() -> list[Player]: + """Load the player roster once from the bundled CSV. + + Missing salary/college cells are kept as the string "NaN" to match the + original pandas-backed behavior (empty cells became NaN floats). + Returns: + The full list of players. + """ players: list[Player] = [] + with _CSV_PATH.open(newline="") as f: + for row in csv.DictReader(f): + salary = row["salary"] + players.append( + Player( + name=row["name"], + team=row["team"], + number=int(row["number"]), + position=row["position"], + age=int(row["age"]), + height=row["height"], + weight=int(row["weight"]), + college=row["college"] or "NaN", + salary=int(salary) if salary else "NaN", + ) + ) + return players + + +# The roster is identical for every session, so load it once at import time +# and keep it server-side instead of re-reading the CSV and stashing it in +# per-connection state. +PLAYERS: list[Player] = _load_players() + +_SEARCH_ATTRS = ( + "name", + "team", + "number", + "position", + "age", + "height", + "weight", + "college", + "salary", +) + + +class State(rx.State): + """Table-tab state: search, sort and pagination over the roster.""" search_value: str = "" - sort_value: str = "" + sort_value: str = "name" # Matches the "Sort By: Name" select default. sort_reverse: bool = False - total_items: int = 0 + total_items: int = len(PLAYERS) offset: int = 0 limit: int = 12 # Number of rows per page - selected_items: Dict[str, List] = ( - all_items # We add all items to the selected items by default - ) - age: tuple[int, int] = (19, 40) - salary: tuple[int, int] = (0, 25000000) - - @rx.event - def set_age(self, value: list[int | float]): - self.age = (int(value[0]), int(value[1])) - - @rx.event - def set_salary(self, value: list[int | float]): - self.salary = (int(value[0]), int(value[1])) - @rx.event def set_sort_value(self, value: str): self.sort_value = value @@ -46,12 +78,13 @@ def set_search_value(self, value: str): self.search_value = value @rx.var(cache=True) - def filtered_sorted_players(self) -> list[Player]: - players = self.players + def _filtered_sorted_players(self) -> list[Player]: + # Backend-only (leading underscore): the full filtered list never + # crosses the wire; only `get_current_page` is sent to the client. + players = PLAYERS - # Filter players based on selected item if self.sort_value: - if self.sort_value in ["salary", "number"]: + if self.sort_value in ("salary", "number"): players = sorted( players, key=lambda player: float(getattr(player, self.sort_value)), @@ -64,7 +97,6 @@ def filtered_sorted_players(self) -> list[Player]: reverse=self.sort_reverse, ) - # Filter players based on search value if self.search_value: search_value = self.search_value.lower() players = [ @@ -72,17 +104,7 @@ def filtered_sorted_players(self) -> list[Player]: for player in players if any( search_value in str(getattr(player, attr)).lower() - for attr in [ - "name", - "team", - "number", - "position", - "age", - "height", - "weight", - "college", - "salary", - ] + for attr in _SEARCH_ATTRS ) ] @@ -98,11 +120,9 @@ def total_pages(self) -> int: 1 if self.total_items % self.limit else 0 ) - @rx.var(cache=True, initial_value=[]) + @rx.var(cache=True) def get_current_page(self) -> list[Player]: - start_index = self.offset - end_index = start_index + self.limit - return self.filtered_sorted_players[start_index:end_index] + return self._filtered_sorted_players[self.offset : self.offset + self.limit] def prev_page(self): if self.page_number > 1: @@ -118,223 +138,142 @@ def first_page(self): def last_page(self): self.offset = (self.total_pages - 1) * self.limit - def load_entries(self): - df = pd.read_csv(nba_csv) - df = df.replace("", np.nan) # Replace empty strings with NaN - self.players = [Player(**row) for _, row in df.iterrows()] - self.total_items = len(self.players) - def toggle_sort(self): self.sort_reverse = not self.sort_reverse - self.load_entries() + + +class StatsState(rx.State): + """Stats-tab state: selections, range filters and chart aggregations. + + Kept separate from `State` so none of the chart data is computed or sent + to the client while the Table tab is showing. + """ + + selected_items: Dict[str, List] = all_items # All items are selected by default. + age: tuple[int, int] = (19, 40) + salary: tuple[int, int] = (0, 25000000) + + stats_view: str = "age_salary" + radar_toggle: bool = False + area_toggle: bool = False + + @rx.event + def set_age(self, value: list[int | float]): + self.age = (int(value[0]), int(value[1])) + + @rx.event + def set_salary(self, value: list[int | float]): + self.salary = (int(value[0]), int(value[1])) + + @rx.event + def set_stats_view(self, value: str): + self.stats_view = value + + def toggle_radarchart(self): + self.radar_toggle = not self.radar_toggle + + def toggle_areachart(self): + self.area_toggle = not self.area_toggle + + def add_selected(self, list_name: str, item: str): + self.selected_items[list_name].append(item) + + def remove_selected(self, list_name: str, item: str): + self.selected_items[list_name].remove(item) + + def add_all_selected(self, list_name: str): + self.selected_items[list_name] = list(all_items[list_name]) + + def clear_selected(self, list_name: str): + self.selected_items[list_name].clear() + + def random_selected(self, list_name: str): + items = all_items[list_name] + self.selected_items[list_name] = random.sample( + items, random.randint(1, len(items)) + ) + + def _passes_filters(self, player: Player) -> bool: + """Whether a player matches the current team/college/position/age/salary selection. + + Args: + player: The player to test. + + Returns: + True if the player should be included in the charts. + """ + return ( + player.salary != "NaN" + and player.team in self.selected_items["teams"] + and player.college in self.selected_items["colleges"] + and player.position in self.selected_items["positions"] + and self.age[0] <= player.age <= self.age[1] + and self.salary[0] <= float(player.salary) <= self.salary[1] + ) + + def _average_by(self, group_attr: str, value_attr: str) -> dict: + """Average ``value_attr`` over chart-filtered players, grouped by ``group_attr``. + + Args: + group_attr: Player attribute to group on (e.g. "team", "age"). + value_attr: Player attribute to average within each group. + + Returns: + Each group value mapped to its rounded average, in first-seen order. + """ + grouped: dict = {} + for player in PLAYERS: + if self._passes_filters(player): + grouped.setdefault(getattr(player, group_attr), []).append( + float(getattr(player, value_attr)) + ) + return { + key: round(sum(values) / len(values), 2) + for key, values in grouped.items() + } @rx.var(cache=True) def get_age_salary_chart_data(self) -> list[dict]: - age_salary_data = {} - age_count = {} - - for player in self.players: - if ( - not pd.isna(player.age) - and not pd.isna(player.salary) - and player.team in self.selected_items["teams"] - and player.college in self.selected_items["colleges"] - and player.position in self.selected_items["positions"] - and self.age[0] <= player.age <= self.age[1] - and self.salary[0] <= float(player.salary) <= self.salary[1] - ): - age = player.age - if age not in age_salary_data: - age_salary_data[age] = 0 - age_count[age] = 0 - - age_salary_data[age] += float(player.salary) - age_count[age] += 1 - + averages = self._average_by("age", "salary") return [ - { - "age": age, - "average salary": round( - age_salary_data.get(age, 0) / age_count.get(age, 1), 2 - ), - } - for age in range(self.age[0], self.age[1] + 1) # Ensure we include all ages + # Include every age in range, even ones with no matching players. + {"age": age, "average salary": averages.get(age, 0)} + for age in range(self.age[0], self.age[1] + 1) ] @rx.var(cache=True) def get_position_salary_chart_data(self) -> list[dict]: - position_salary_data = {} - position_count = {} - - for player in self.players: - if ( - not pd.isna(player.position) - and not pd.isna(player.salary) - and player.team in self.selected_items["teams"] - and player.college in self.selected_items["colleges"] - and player.position in self.selected_items["positions"] - and self.age[0] <= player.age <= self.age[1] - and self.salary[0] <= float(player.salary) <= self.salary[1] - ): - position = player.position - if position not in position_salary_data: - position_salary_data[position] = 0 - position_count[position] = 0 - - position_salary_data[position] += float(player.salary) - position_count[position] += 1 - return [ - { - "position": position, - "average salary": round( - position_salary_data[position] / position_count[position], 2 - ), - } - for position in position_salary_data + {"position": position, "average salary": avg} + for position, avg in self._average_by("position", "salary").items() ] @rx.var(cache=True) def get_team_salary_chart_data(self) -> list[dict]: - team_salary_data = {} - team_count = {} - - for player in self.players: - if ( - not pd.isna(player.team) - and not pd.isna(player.salary) - and player.team in self.selected_items["teams"] - and player.college in self.selected_items["colleges"] - and player.position in self.selected_items["positions"] - and self.age[0] <= player.age <= self.age[1] - and self.salary[0] <= float(player.salary) <= self.salary[1] - ): - team = player.team - if team not in team_salary_data: - team_salary_data[team] = 0 - team_count[team] = 0 - - team_salary_data[team] += float(player.salary) - team_count[team] += 1 - return [ - { - "team": team, - "average salary": round(team_salary_data[team] / team_count[team], 2), - } - for team in team_salary_data + {"team": team, "average salary": avg} + for team, avg in self._average_by("team", "salary").items() ] @rx.var(cache=True) def get_college_salary_chart_data(self) -> list[dict]: - college_salary_data = {} - college_count = {} - - for player in self.players: - if ( - not pd.isna(player.college) - and not pd.isna(player.salary) - and player.team in self.selected_items["teams"] - and player.college in self.selected_items["colleges"] - and player.position in self.selected_items["positions"] - and self.age[0] <= player.age <= self.age[1] - and self.salary[0] <= float(player.salary) <= self.salary[1] - ): - college = player.college - if college not in college_salary_data: - college_salary_data[college] = 0 - college_count[college] = 0 - - college_salary_data[college] += float(player.salary) - college_count[college] += 1 - + # Players with no college are already dropped by `_passes_filters` + # (they can't be in the selected colleges list). return [ - { - "college": college, - "average salary": round( - college_salary_data[college] / college_count[college], 2 - ), - } - for college in college_salary_data + {"college": college, "average salary": avg} + for college, avg in self._average_by("college", "salary").items() ] @rx.var(cache=True) def get_team_age_average_data(self) -> list[dict]: - team_age_data = {} - team_count = {} - - for player in self.players: - if ( - not pd.isna(player.team) - and not pd.isna(player.age) - and player.team in self.selected_items["teams"] - and player.college in self.selected_items["colleges"] - and player.position in self.selected_items["positions"] - and self.age[0] <= player.age <= self.age[1] - and self.salary[0] <= float(player.salary) <= self.salary[1] - ): - team = player.team - if team not in team_age_data: - team_age_data[team] = [] - team_count[team] = 0 - - team_age_data[team].append(player.age) - team_count[team] += 1 - return [ - { - "team": team, - "average age": round(sum(ages) / team_count[team], 2), - } - for team, ages in team_age_data.items() + {"team": team, "average age": avg} + for team, avg in self._average_by("team", "age").items() ] @rx.var(cache=True) def get_position_age_average_data(self) -> list[dict]: - position_age_data = {} - position_count = {} - - for player in self.players: - if ( - not pd.isna(player.position) - and not pd.isna(player.age) - and player.team in self.selected_items["teams"] - and player.college in self.selected_items["colleges"] - and player.position in self.selected_items["positions"] - and self.age[0] <= player.age <= self.age[1] - and self.salary[0] <= float(player.salary) <= self.salary[1] - ): - position = player.position - if position not in position_age_data: - position_age_data[position] = [] - position_count[position] = 0 - - position_age_data[position].append(player.age) - position_count[position] += 1 - return [ - { - "position": position, - "average age": round(sum(ages) / position_count[position], 2), - } - for position, ages in position_age_data.items() + {"position": position, "average age": avg} + for position, avg in self._average_by("position", "age").items() ] - - def add_selected(self, list_name: str, item: str): - self.selected_items[list_name].append(item) - - def remove_selected(self, list_name: str, item: str): - self.selected_items[list_name].remove(item) - - def add_all_selected(self, list_name: str): - self.selected_items[list_name] = list(all_items[list_name]) - - def clear_selected(self, list_name: str): - self.selected_items[list_name].clear() - - def random_selected(self, list_name: str): - self.selected_items[list_name] = np.random.choice( - all_items[list_name], - size=np.random.randint(1, len(all_items[list_name]) + 1), - replace=False, - ).tolist() diff --git a/nba/nba/components/item_badges.py b/nba/nba/components/item_badges.py index 22ca31d..714c6e8 100644 --- a/nba/nba/components/item_badges.py +++ b/nba/nba/components/item_badges.py @@ -5,7 +5,7 @@ LiteralAccentColor, ) -from ..backend.backend import State +from ..backend.backend import StatsState def _get_item_color( @@ -32,7 +32,7 @@ def _selected_item_badge( rx.icon("x", size=18), color_scheme=_get_item_color(item, items_dict), **badge_props, - on_click=lambda: State.remove_selected(item_name, item), + on_click=lambda: StatsState.remove_selected(item_name, item), ) @@ -40,14 +40,14 @@ def _unselected_item_badge( item_name: str, items_dict: Dict[str, LiteralAccentColor], items: List ) -> rx.Component: return rx.cond( - State.selected_items[item_name].contains(items[0]), + StatsState.selected_items[item_name].contains(items[0]), rx.box(), rx.badge( items[0], rx.icon("plus", size=18), color_scheme=_get_item_color(items[0], items_dict), **badge_props, - on_click=lambda: State.add_selected(item_name, items[0]), + on_click=lambda: StatsState.add_selected(item_name, items[0]), ), ) diff --git a/nba/nba/components/stats_selector.py b/nba/nba/components/stats_selector.py index 1ba4f7b..aac8c53 100644 --- a/nba/nba/components/stats_selector.py +++ b/nba/nba/components/stats_selector.py @@ -1,6 +1,6 @@ import reflex as rx -from ..backend.backend import State +from ..backend.backend import StatsState from ..backend.data_items import college_dict, position_dict, teams_dict from .item_badges import _selected_item_badge, _unselected_item_badge @@ -44,9 +44,9 @@ def _items_selector(item: str, items_dict: dict) -> rx.Component: return rx.vstack( rx.flex( rx.hstack( - _add_all_button(State.add_all_selected(item)), - _clear_button(State.clear_selected(item)), - _random_button(State.random_selected(item)), + _add_all_button(StatsState.add_all_selected(item)), + _clear_button(StatsState.clear_selected(item)), + _random_button(StatsState.random_selected(item)), spacing="2", justify="end", width="100%", @@ -57,7 +57,7 @@ def _items_selector(item: str, items_dict: dict) -> rx.Component: ), rx.flex( rx.foreach( - State.selected_items[item], + StatsState.selected_items[item], lambda team: _selected_item_badge(item, items_dict, team), ), wrap="wrap", @@ -76,7 +76,7 @@ def _items_selector(item: str, items_dict: dict) -> rx.Component: def _accordion_header_stat(icon: str, text: str, item: str) -> rx.Component: return rx.hstack( rx.icon(icon, size=24), - rx.heading(text + f" ({(State.selected_items[item].length())})", size="5"), + rx.heading(text + f" ({(StatsState.selected_items[item].length())})", size="5"), spacing="2", align="center", width="100%", @@ -100,12 +100,12 @@ def _age_selector() -> rx.Component: min=19, variant="soft", max=40, - on_change=State.set_age, + on_change=StatsState.set_age, ), rx.hstack( - rx.badge("Min Age: ", State.age[0]), + rx.badge("Min Age: ", StatsState.age[0]), rx.spacer(), - rx.badge("Max Age: ", State.age[1]), + rx.badge("Max Age: ", StatsState.age[1]), width="100%", ), width="100%", @@ -119,12 +119,12 @@ def _salary_selector() -> rx.Component: min=0, variant="soft", max=25000000, - on_value_commit=State.set_salary, + on_value_commit=StatsState.set_salary, ), rx.hstack( - rx.badge("Min Salary: ", State.salary[0]), + rx.badge("Min Salary: ", StatsState.salary[0]), rx.spacer(), - rx.badge("Max Salary: ", State.salary[1]), + rx.badge("Max Salary: ", StatsState.salary[1]), width="100%", ), width="100%", diff --git a/nba/nba/nba.py b/nba/nba/nba.py index 8a38bee..061eb1c 100644 --- a/nba/nba/nba.py +++ b/nba/nba/nba.py @@ -1,6 +1,5 @@ import reflex as rx -from .backend.backend import State from .views.navbar import navbar from .views.stats import stats_ui from .views.table import main_table @@ -65,7 +64,6 @@ def index() -> rx.Component: ) app.add_page( index, - on_load=State.load_entries, title="NBA Data", description="NBA Data for the 2015-2016 season.", ) diff --git a/nba/nba/views/stats.py b/nba/nba/views/stats.py index b1143b6..0879d50 100644 --- a/nba/nba/views/stats.py +++ b/nba/nba/views/stats.py @@ -1,25 +1,9 @@ import reflex as rx -from ..backend.backend import State +from ..backend.backend import StatsState from ..components.stats_selector import stats_selector -class StatsState(rx.State): - stats_view: str = "age_salary" - radar_toggle: bool = False - area_toggle: bool = False - - def toggle_radarchart(self): - self.radar_toggle = not self.radar_toggle - - def toggle_areachart(self): - self.area_toggle = not self.area_toggle - - @rx.event - def set_stats_view(self, value: str): - self.stats_view = value - - def _age_salary_chart() -> rx.Component: return rx.cond( StatsState.area_toggle, @@ -35,7 +19,7 @@ def _age_salary_chart() -> rx.Component: ), rx.recharts.x_axis(data_key="age"), rx.recharts.y_axis(type_="number", scale="auto", hide=True), - data=State.get_age_salary_chart_data, + data=StatsState.get_age_salary_chart_data, min_height=325, ), rx.recharts.bar_chart( @@ -47,7 +31,7 @@ def _age_salary_chart() -> rx.Component: ), rx.recharts.x_axis(data_key="age"), rx.recharts.y_axis(type_="number", scale="auto", hide=True), - data=State.get_age_salary_chart_data, + data=StatsState.get_age_salary_chart_data, min_height=325, ), ) @@ -66,7 +50,7 @@ def _position_salary_chart() -> rx.Component: ), rx.recharts.polar_grid(), rx.recharts.polar_angle_axis(data_key="position"), - data=State.get_position_salary_chart_data, + data=StatsState.get_position_salary_chart_data, min_height=325, ), rx.recharts.bar_chart( @@ -78,7 +62,7 @@ def _position_salary_chart() -> rx.Component: ), rx.recharts.x_axis(data_key="position"), rx.recharts.y_axis(type_="number", scale="auto", hide=True), - data=State.get_position_salary_chart_data, + data=StatsState.get_position_salary_chart_data, min_height=325, ), ) @@ -100,7 +84,7 @@ def _team_salary_chart() -> rx.Component: rx.recharts.brush(data_key="name", height=30, stroke="#8E4EC6"), rx.recharts.x_axis(data_key="team"), rx.recharts.y_axis(type_="number", scale="auto", hide=True), - data=State.get_team_salary_chart_data, + data=StatsState.get_team_salary_chart_data, min_height=325, ), rx.recharts.bar_chart( @@ -115,7 +99,7 @@ def _team_salary_chart() -> rx.Component: rx.recharts.brush(data_key="name", height=30, stroke="#8E4EC6"), rx.recharts.x_axis(data_key="team"), rx.recharts.y_axis(type_="number", scale="auto", hide=True), - data=State.get_team_salary_chart_data, + data=StatsState.get_team_salary_chart_data, min_height=325, ), ) @@ -137,7 +121,7 @@ def _college_salary_chart() -> rx.Component: rx.recharts.brush(data_key="name", height=30, stroke="#fdc313"), rx.recharts.x_axis(data_key="college"), rx.recharts.y_axis(type_="number", scale="auto", hide=True), - data=State.get_college_salary_chart_data, + data=StatsState.get_college_salary_chart_data, min_height=325, ), rx.recharts.bar_chart( @@ -150,7 +134,7 @@ def _college_salary_chart() -> rx.Component: rx.recharts.brush(data_key="name", height=30, stroke="#fdc313"), rx.recharts.x_axis(data_key="college"), rx.recharts.y_axis(type_="number", scale="auto", hide=True), - data=State.get_college_salary_chart_data, + data=StatsState.get_college_salary_chart_data, min_height=325, ), ) @@ -172,7 +156,7 @@ def _age_team_chart() -> rx.Component: rx.recharts.brush(data_key="team", height=30, stroke="#FFA500"), rx.recharts.x_axis(data_key="team"), rx.recharts.y_axis(type_="number", scale="auto", hide=True), - data=State.get_team_age_average_data, + data=StatsState.get_team_age_average_data, min_height=325, ), rx.recharts.bar_chart( @@ -183,7 +167,7 @@ def _age_team_chart() -> rx.Component: rx.recharts.brush(data_key="team", height=30, stroke="#FFA500"), rx.recharts.x_axis(data_key="team"), rx.recharts.y_axis(type_="number", scale="auto", hide=True), - data=State.get_team_age_average_data, + data=StatsState.get_team_age_average_data, min_height=325, ), ) @@ -202,7 +186,7 @@ def _age_position_chart() -> rx.Component: ), rx.recharts.polar_grid(), rx.recharts.polar_angle_axis(data_key="position"), - data=State.get_position_age_average_data, + data=StatsState.get_position_age_average_data, min_height=325, ), rx.recharts.bar_chart( @@ -213,7 +197,7 @@ def _age_position_chart() -> rx.Component: rx.recharts.brush(data_key="position", height=30, stroke="#E54666"), rx.recharts.x_axis(data_key="position"), rx.recharts.y_axis(type_="number", scale="auto", hide=True), - data=State.get_position_age_average_data, + data=StatsState.get_position_age_average_data, min_height=325, ), ) From f38ef3b185cabd203b190b13da51cf8828747a2a Mon Sep 17 00:00:00 2001 From: Farhan Ali Raza Date: Tue, 2 Jun 2026 02:45:31 +0500 Subject: [PATCH 2/5] style: collapse _average_by dict comprehension onto one line --- nba/nba/backend/backend.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nba/nba/backend/backend.py b/nba/nba/backend/backend.py index 836b5e1..baeb14c 100644 --- a/nba/nba/backend/backend.py +++ b/nba/nba/backend/backend.py @@ -228,8 +228,7 @@ def _average_by(self, group_attr: str, value_attr: str) -> dict: float(getattr(player, value_attr)) ) return { - key: round(sum(values) / len(values), 2) - for key, values in grouped.items() + key: round(sum(values) / len(values), 2) for key, values in grouped.items() } @rx.var(cache=True) From 2d23b1d9d02c2e80e642a515a3c4e6e8c7e6c6a5 Mon Sep 17 00:00:00 2001 From: Farhan Ali Raza Date: Tue, 2 Jun 2026 03:02:18 +0500 Subject: [PATCH 3/5] ci: install reflex workspace from source so split packages match reflex is a uv workspace, so reflex and its split-out sibling packages (reflex-base, reflex-components-*) must be installed together from the same tree. The old `pip install reflex@git` pulled released sub-packages from PyPI and broke whenever main moved a module between packages. Check out the monorepo to .reflex-src and `uv pip install` it instead, and rename the workflow input from reflex_dep (raw pip spec) to reflex_ref (git ref). --- .github/workflows/check_export.yml | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/.github/workflows/check_export.yml b/.github/workflows/check_export.yml index 51ea53d..23958ab 100644 --- a/.github/workflows/check_export.yml +++ b/.github/workflows/check_export.yml @@ -1,6 +1,10 @@ name: check-export env: - REFLEX_DEP: "git+https://github.com/reflex-dev/reflex.git@main" + # Git ref of the reflex monorepo to test templates against. reflex is a uv + # workspace, so reflex and its split-out packages (reflex-base, + # reflex-components-*) have to be installed together from the same tree — + # a dev reflex paired with released sub-packages from PyPI does not work. + REFLEX_REF: "main" REFLEX_TELEMETRY_ENABLED: false on: push: @@ -9,8 +13,9 @@ on: branches: [main] workflow_dispatch: inputs: - reflex_dep: - description: "Reflex dep (raw pip spec)" + reflex_ref: + description: "reflex monorepo git ref to test against" + default: "main" jobs: list-templates: @@ -27,6 +32,12 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: recursive + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + repository: reflex-dev/reflex + ref: ${{ github.event.inputs.reflex_ref || env.REFLEX_REF }} + path: .reflex-src + fetch-depth: 0 # full history so uv-dynamic-versioning derives a real version - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: python-version: 3.10.16 @@ -53,7 +64,14 @@ jobs: python -m venv venv source venv/bin/activate - pip install '${{ github.event.inputs.reflex_dep || env.REFLEX_DEP }}' -r requirements.txt + # Install the whole reflex workspace from the checked-out monorepo, so + # reflex and every split-out package (reflex-base, reflex-components-*) + # are the same dev version. uv resolves the sibling workspace packages + # from the source tree; plain `pip install reflex@git` would instead + # pull released sub-packages from PyPI and break whenever main moves a + # module between packages (e.g. reflex_base.components.memo). + pip install uv + uv pip install "$GITHUB_WORKSPACE/.reflex-src" -r requirements.txt export OPENAI_API_KEY="dummy" reflex init From 26f6f570f6542ebaa8ffd2a6ab40cab68bfa0891 Mon Sep 17 00:00:00 2001 From: Farhan Ali Raza Date: Tue, 2 Jun 2026 03:12:08 +0500 Subject: [PATCH 4/5] fix: represent missing nba data as None and keep pagination in sync MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Empty salary/college cells now parse to None (Player fields become Optional) instead of the string "NaN", so sorting, filtering, search and averaging work on real values without special-casing a sentinel. Missing values always sort to the end and render as "—" in the table. Derive total_items from the filtered list, reset offset to 0 on a new search, and floor total_pages at 1, so the page count and navigation can never drift past the actual matches. Also move the Radix theme from rx.App(theme=...) to a RadixThemesPlugin in rxconfig. --- .../account_management_dashboard.py | 9 +-- account_management_dashboard/rxconfig.py | 13 ++++- .../admin_dashboard/admin_dashboard.py | 2 +- admin_dashboard/rxconfig.py | 6 +- admin_panel/admin_panel/admin_panel.py | 1 - admin_panel/rxconfig.py | 6 +- ai_image_gen/ai_image_gen/ai_image_gen.py | 7 --- ai_image_gen/rxconfig.py | 13 ++++- .../business_analytics_dashboard.py | 2 +- business_analytics_dashboard/rxconfig.py | 6 +- chat_app/chat_app/chat_app.py | 2 +- chat_app/rxconfig.py | 6 +- .../company_dashboard/company_dashboard.py | 1 - company_dashboard/rxconfig.py | 6 +- .../customer_data/customer_data.py | 6 +- customer_data_app/rxconfig.py | 12 +++- dalle/dalle/dalle.py | 6 +- dalle/rxconfig.py | 12 +++- .../futuristic_dashboard.py | 1 - futuristic_dashboard/rxconfig.py | 6 +- .../manufacturing_dashboard.py | 1 - manufacturing_dashboard/rxconfig.py | 6 +- nba/nba/backend/backend.py | 56 ++++++++++++------- nba/nba/backend/player.py | 6 +- nba/nba/nba.py | 3 - nba/nba/views/table.py | 4 +- nba/rxconfig.py | 12 +++- .../retail_analytics_dashboard.py | 2 +- retail_analytics_dashboard/rxconfig.py | 6 +- .../retail_dashboard/retail_dashboard.py | 1 - retail_dashboard/rxconfig.py | 6 +- .../retention_dashboard.py | 2 +- retention_dashboard/rxconfig.py | 6 +- sales/rxconfig.py | 12 +++- sales/sales/sales.py | 6 +- space_dashboard/rxconfig.py | 6 +- .../space_dashboard/space_dashboard.py | 2 +- stock_graph_app/rxconfig.py | 6 +- .../stock_graph_app/stock_graph_app.py | 1 - stock_market_dashboard/rxconfig.py | 6 +- .../stock_market_dashboard.py | 1 - table_dashboard/rxconfig.py | 6 +- .../table_dashboard/table_dashboard.py | 2 +- text_annotation_app/rxconfig.py | 6 +- .../text_annotation_app.py | 2 +- 45 files changed, 195 insertions(+), 95 deletions(-) diff --git a/account_management_dashboard/account_management_dashboard/account_management_dashboard.py b/account_management_dashboard/account_management_dashboard/account_management_dashboard.py index d470b2c..31e64e6 100644 --- a/account_management_dashboard/account_management_dashboard/account_management_dashboard.py +++ b/account_management_dashboard/account_management_dashboard/account_management_dashboard.py @@ -44,12 +44,5 @@ def index() -> rx.Component: ) -app = rx.App( - theme=rx.theme( - appearance="light", - has_background=False, - radius="medium", - accent_color="indigo", - ) -) +app = rx.App() app.add_page(index, title="Accounts Dashboard") diff --git a/account_management_dashboard/rxconfig.py b/account_management_dashboard/rxconfig.py index de087c4..8d3003d 100644 --- a/account_management_dashboard/rxconfig.py +++ b/account_management_dashboard/rxconfig.py @@ -2,5 +2,16 @@ config = rx.Config( app_name="account_management_dashboard", - plugins=[rx.plugins.SitemapPlugin(), rx.plugins.TailwindV3Plugin()], + plugins=[ + rx.plugins.SitemapPlugin(), + rx.plugins.TailwindV3Plugin(), + rx.plugins.RadixThemesPlugin( + theme=rx.theme( + appearance="light", + has_background=False, + radius="medium", + accent_color="indigo", + ) + ), + ], ) diff --git a/admin_dashboard/admin_dashboard/admin_dashboard.py b/admin_dashboard/admin_dashboard/admin_dashboard.py index 85801d7..a101c29 100644 --- a/admin_dashboard/admin_dashboard/admin_dashboard.py +++ b/admin_dashboard/admin_dashboard/admin_dashboard.py @@ -59,7 +59,7 @@ def customer_success_hub_page() -> rx.Component: return mock_page("Customer Success Hub") -app = rx.App(theme=rx.theme(appearance="light"), stylesheets=[]) +app = rx.App(stylesheets=[]) app.add_page(index, route="/") app.add_page(sales_pipeline_page, route="/sales-pipeline") app.add_page(hr_portal_page, route="/hr-portal") diff --git a/admin_dashboard/rxconfig.py b/admin_dashboard/rxconfig.py index 363ba29..a1f56a5 100644 --- a/admin_dashboard/rxconfig.py +++ b/admin_dashboard/rxconfig.py @@ -2,5 +2,9 @@ config = rx.Config( app_name="admin_dashboard", - plugins=[rx.plugins.SitemapPlugin(), rx.plugins.TailwindV3Plugin()], + plugins=[ + rx.plugins.SitemapPlugin(), + rx.plugins.TailwindV3Plugin(), + rx.plugins.RadixThemesPlugin(theme=rx.theme(appearance="light")), + ], ) diff --git a/admin_panel/admin_panel/admin_panel.py b/admin_panel/admin_panel/admin_panel.py index 7633f5c..76c29b0 100644 --- a/admin_panel/admin_panel/admin_panel.py +++ b/admin_panel/admin_panel/admin_panel.py @@ -34,7 +34,6 @@ def index() -> rx.Component: app = rx.App( - theme=rx.theme(appearance="light"), head_components=[ rx.el.link( rel="preconnect", diff --git a/admin_panel/rxconfig.py b/admin_panel/rxconfig.py index 2ff65d9..78e73de 100644 --- a/admin_panel/rxconfig.py +++ b/admin_panel/rxconfig.py @@ -2,5 +2,9 @@ config = rx.Config( app_name="admin_panel", - plugins=[rx.plugins.SitemapPlugin(), rx.plugins.TailwindV3Plugin()], + plugins=[ + rx.plugins.SitemapPlugin(), + rx.plugins.TailwindV3Plugin(), + rx.plugins.RadixThemesPlugin(theme=rx.theme(appearance="light")), + ], ) diff --git a/ai_image_gen/ai_image_gen/ai_image_gen.py b/ai_image_gen/ai_image_gen/ai_image_gen.py index 16a3f64..48c3b88 100644 --- a/ai_image_gen/ai_image_gen/ai_image_gen.py +++ b/ai_image_gen/ai_image_gen/ai_image_gen.py @@ -10,11 +10,4 @@ stylesheets=styles.base_stylesheets, html_lang="en", html_custom_attrs={"className": "!scroll-smooth"}, - theme=rx.theme( - appearance="inherit", - has_background=True, - scaling="100%", - radius="none", - accent_color="violet", - ), ) diff --git a/ai_image_gen/rxconfig.py b/ai_image_gen/rxconfig.py index 75c18cd..e15ed19 100644 --- a/ai_image_gen/rxconfig.py +++ b/ai_image_gen/rxconfig.py @@ -2,5 +2,16 @@ config = rx.Config( app_name="ai_image_gen", - plugins=[rx.plugins.SitemapPlugin()], + plugins=[ + rx.plugins.SitemapPlugin(), + rx.plugins.RadixThemesPlugin( + theme=rx.theme( + appearance="inherit", + has_background=True, + scaling="100%", + radius="none", + accent_color="violet", + ) + ), + ], ) diff --git a/business_analytics_dashboard/business_analytics_dashboard/business_analytics_dashboard.py b/business_analytics_dashboard/business_analytics_dashboard/business_analytics_dashboard.py index 7b26df5..469933a 100644 --- a/business_analytics_dashboard/business_analytics_dashboard/business_analytics_dashboard.py +++ b/business_analytics_dashboard/business_analytics_dashboard/business_analytics_dashboard.py @@ -37,5 +37,5 @@ def index() -> rx.Component: ) -app = rx.App(theme=rx.theme(appearance="light")) +app = rx.App() app.add_page(index) diff --git a/business_analytics_dashboard/rxconfig.py b/business_analytics_dashboard/rxconfig.py index 61eb962..943b8ff 100644 --- a/business_analytics_dashboard/rxconfig.py +++ b/business_analytics_dashboard/rxconfig.py @@ -2,5 +2,9 @@ config = rx.Config( app_name="business_analytics_dashboard", - plugins=[rx.plugins.SitemapPlugin(), rx.plugins.TailwindV3Plugin()], + plugins=[ + rx.plugins.SitemapPlugin(), + rx.plugins.TailwindV3Plugin(), + rx.plugins.RadixThemesPlugin(theme=rx.theme(appearance="light")), + ], ) diff --git a/chat_app/chat_app/chat_app.py b/chat_app/chat_app/chat_app.py index 46bdc99..f5b5c88 100644 --- a/chat_app/chat_app/chat_app.py +++ b/chat_app/chat_app/chat_app.py @@ -8,5 +8,5 @@ def index() -> rx.Component: return chat_interface() -app = rx.App(theme=rx.theme(appearance="light")) +app = rx.App() app.add_page(index) diff --git a/chat_app/rxconfig.py b/chat_app/rxconfig.py index ae39db3..bdc99aa 100644 --- a/chat_app/rxconfig.py +++ b/chat_app/rxconfig.py @@ -2,5 +2,9 @@ config = rx.Config( app_name="chat_app", - plugins=[rx.plugins.SitemapPlugin(), rx.plugins.TailwindV3Plugin()], + plugins=[ + rx.plugins.SitemapPlugin(), + rx.plugins.TailwindV3Plugin(), + rx.plugins.RadixThemesPlugin(theme=rx.theme(appearance="light")), + ], ) diff --git a/company_dashboard/company_dashboard/company_dashboard.py b/company_dashboard/company_dashboard/company_dashboard.py index 2e342fe..6eb9d59 100644 --- a/company_dashboard/company_dashboard/company_dashboard.py +++ b/company_dashboard/company_dashboard/company_dashboard.py @@ -32,7 +32,6 @@ def index() -> rx.Component: app = rx.App( - theme=rx.theme(appearance="light"), stylesheets=["https://cdn.tailwindcss.com"], style={ rx.el.label: {"font_family": "JetBrains Mono,ui-monospace,monospace"}, diff --git a/company_dashboard/rxconfig.py b/company_dashboard/rxconfig.py index 9e08707..d8b0af7 100644 --- a/company_dashboard/rxconfig.py +++ b/company_dashboard/rxconfig.py @@ -2,5 +2,9 @@ config = rx.Config( app_name="company_dashboard", - plugins=[rx.plugins.SitemapPlugin(), rx.plugins.TailwindV3Plugin()], + plugins=[ + rx.plugins.SitemapPlugin(), + rx.plugins.TailwindV3Plugin(), + rx.plugins.RadixThemesPlugin(theme=rx.theme(appearance="light")), + ], ) diff --git a/customer_data_app/customer_data/customer_data.py b/customer_data_app/customer_data/customer_data.py index a121408..9560794 100644 --- a/customer_data_app/customer_data/customer_data.py +++ b/customer_data_app/customer_data/customer_data.py @@ -19,11 +19,7 @@ def index() -> rx.Component: ) -app = rx.App( - theme=rx.theme( - appearance="dark", has_background=True, radius="large", accent_color="grass" - ), -) +app = rx.App() app.add_page( index, diff --git a/customer_data_app/rxconfig.py b/customer_data_app/rxconfig.py index 826fb65..680de22 100644 --- a/customer_data_app/rxconfig.py +++ b/customer_data_app/rxconfig.py @@ -7,5 +7,15 @@ config = rx.Config( app_name="customer_data", db_url=database_url, - plugins=[rx.plugins.SitemapPlugin()], + plugins=[ + rx.plugins.SitemapPlugin(), + rx.plugins.RadixThemesPlugin( + theme=rx.theme( + appearance="dark", + has_background=True, + radius="large", + accent_color="grass", + ) + ), + ], ) diff --git a/dalle/dalle/dalle.py b/dalle/dalle/dalle.py index 2fc8e66..8ab5702 100644 --- a/dalle/dalle/dalle.py +++ b/dalle/dalle/dalle.py @@ -87,9 +87,5 @@ def index(): # Add state and page to the app. -app = rx.App( - theme=rx.theme( - appearance="light", has_background=True, radius="medium", accent_color="mint" - ), -) +app = rx.App() app.add_page(index, title="Reflex:DALL-E") diff --git a/dalle/rxconfig.py b/dalle/rxconfig.py index 52dbaca..e0b663b 100644 --- a/dalle/rxconfig.py +++ b/dalle/rxconfig.py @@ -2,5 +2,15 @@ config = rx.Config( app_name="dalle", - plugins=[rx.plugins.SitemapPlugin()], + plugins=[ + rx.plugins.SitemapPlugin(), + rx.plugins.RadixThemesPlugin( + theme=rx.theme( + appearance="light", + has_background=True, + radius="medium", + accent_color="mint", + ) + ), + ], ) diff --git a/futuristic_dashboard/futuristic_dashboard/futuristic_dashboard.py b/futuristic_dashboard/futuristic_dashboard/futuristic_dashboard.py index f5ccf1b..1defab3 100644 --- a/futuristic_dashboard/futuristic_dashboard/futuristic_dashboard.py +++ b/futuristic_dashboard/futuristic_dashboard/futuristic_dashboard.py @@ -33,7 +33,6 @@ def index() -> rx.Component: app = rx.App( - theme=rx.theme(appearance="light"), stylesheets=[ "https://cdnjs.cloudflare.com/ajax/libs/feather-icons/4.29.0/feather.min.js" ], diff --git a/futuristic_dashboard/rxconfig.py b/futuristic_dashboard/rxconfig.py index 540e637..f79c3e7 100644 --- a/futuristic_dashboard/rxconfig.py +++ b/futuristic_dashboard/rxconfig.py @@ -2,5 +2,9 @@ config = rx.Config( app_name="futuristic_dashboard", - plugins=[rx.plugins.SitemapPlugin(), rx.plugins.TailwindV3Plugin()], + plugins=[ + rx.plugins.SitemapPlugin(), + rx.plugins.TailwindV3Plugin(), + rx.plugins.RadixThemesPlugin(theme=rx.theme(appearance="light")), + ], ) diff --git a/manufacturing_dashboard/manufacturing_dashboard/manufacturing_dashboard.py b/manufacturing_dashboard/manufacturing_dashboard/manufacturing_dashboard.py index bf3e398..5c3a724 100644 --- a/manufacturing_dashboard/manufacturing_dashboard/manufacturing_dashboard.py +++ b/manufacturing_dashboard/manufacturing_dashboard/manufacturing_dashboard.py @@ -26,7 +26,6 @@ def index() -> rx.Component: app = rx.App( - theme=rx.theme(appearance="light"), style={ "font_family": "'Inter', sans-serif", "height": "100%", diff --git a/manufacturing_dashboard/rxconfig.py b/manufacturing_dashboard/rxconfig.py index 17b2c86..18a2e74 100644 --- a/manufacturing_dashboard/rxconfig.py +++ b/manufacturing_dashboard/rxconfig.py @@ -2,5 +2,9 @@ config = rx.Config( app_name="manufacturing_dashboard", - plugins=[rx.plugins.SitemapPlugin(), rx.plugins.TailwindV3Plugin()], + plugins=[ + rx.plugins.SitemapPlugin(), + rx.plugins.TailwindV3Plugin(), + rx.plugins.RadixThemesPlugin(theme=rx.theme(appearance="light")), + ], ) diff --git a/nba/nba/backend/backend.py b/nba/nba/backend/backend.py index baeb14c..165147d 100644 --- a/nba/nba/backend/backend.py +++ b/nba/nba/backend/backend.py @@ -14,8 +14,8 @@ def _load_players() -> list[Player]: """Load the player roster once from the bundled CSV. - Missing salary/college cells are kept as the string "NaN" to match the - original pandas-backed behavior (empty cells became NaN floats). + Empty salary/college cells become None so consumers can sort, filter and + average on real values without special-casing a sentinel string. Returns: The full list of players. @@ -33,8 +33,8 @@ def _load_players() -> list[Player]: age=int(row["age"]), height=row["height"], weight=int(row["weight"]), - college=row["college"] or "NaN", - salary=int(salary) if salary else "NaN", + college=row["college"] or None, + salary=int(salary) if salary else None, ) ) return players @@ -65,7 +65,6 @@ class State(rx.State): sort_value: str = "name" # Matches the "Sort By: Name" select default. sort_reverse: bool = False - total_items: int = len(PLAYERS) offset: int = 0 limit: int = 12 # Number of rows per page @@ -76,6 +75,9 @@ def set_sort_value(self, value: str): @rx.event def set_search_value(self, value: str): self.search_value = value + # A new search resizes the result set, so return to the first page; + # otherwise the old offset could land past the end of the matches. + self.offset = 0 @rx.var(cache=True) def _filtered_sorted_players(self) -> list[Player]: @@ -84,18 +86,19 @@ def _filtered_sorted_players(self) -> list[Player]: players = PLAYERS if self.sort_value: - if self.sort_value in ("salary", "number"): - players = sorted( - players, - key=lambda player: float(getattr(player, self.sort_value)), - reverse=self.sort_reverse, - ) - else: - players = sorted( - players, - key=lambda player: str(getattr(player, self.sort_value)).lower(), - reverse=self.sort_reverse, - ) + attr = self.sort_value + numeric = attr in ("salary", "number") + + def sort_key(player: Player): + value = getattr(player, attr) + return value if numeric else str(value).lower() + + # Players whose value is missing (None salary/college) always sort + # to the end, regardless of direction; the rest sort by value. + present = [p for p in players if getattr(p, attr) is not None] + missing = [p for p in players if getattr(p, attr) is None] + present.sort(key=sort_key, reverse=self.sort_reverse) + players = present + missing if self.search_value: search_value = self.search_value.lower() @@ -103,7 +106,8 @@ def _filtered_sorted_players(self) -> list[Player]: player for player in players if any( - search_value in str(getattr(player, attr)).lower() + (value := getattr(player, attr)) is not None + and search_value in str(value).lower() for attr in _SEARCH_ATTRS ) ] @@ -114,11 +118,21 @@ def _filtered_sorted_players(self) -> list[Player]: def page_number(self) -> int: return (self.offset // self.limit) + 1 + @rx.var(cache=True) + def total_items(self) -> int: + """Number of players matching the current search. + + Derived from the filtered list so the page count and navigation can + never drift out of sync with what the table actually shows. + """ + return len(self._filtered_sorted_players) + @rx.var(cache=True) def total_pages(self) -> int: - return (self.total_items // self.limit) + ( + pages = (self.total_items // self.limit) + ( 1 if self.total_items % self.limit else 0 ) + return max(pages, 1) # Always at least one page, even with no matches. @rx.var(cache=True) def get_current_page(self) -> list[Player]: @@ -203,12 +217,12 @@ def _passes_filters(self, player: Player) -> bool: True if the player should be included in the charts. """ return ( - player.salary != "NaN" + player.salary is not None and player.team in self.selected_items["teams"] and player.college in self.selected_items["colleges"] and player.position in self.selected_items["positions"] and self.age[0] <= player.age <= self.age[1] - and self.salary[0] <= float(player.salary) <= self.salary[1] + and self.salary[0] <= player.salary <= self.salary[1] ) def _average_by(self, group_attr: str, value_attr: str) -> dict: diff --git a/nba/nba/backend/player.py b/nba/nba/backend/player.py index 16997d3..16d3f93 100644 --- a/nba/nba/backend/player.py +++ b/nba/nba/backend/player.py @@ -1,5 +1,5 @@ from dataclasses import dataclass -from typing import Union +from typing import Optional @dataclass @@ -13,5 +13,5 @@ class Player: age: int height: str weight: int - college: str - salary: Union[int, str] # Can also be a string for the NaN values + college: Optional[str] # None when the source cell is empty. + salary: Optional[int] # None when the source cell is empty. diff --git a/nba/nba/nba.py b/nba/nba/nba.py index 061eb1c..15e9ad8 100644 --- a/nba/nba/nba.py +++ b/nba/nba/nba.py @@ -58,9 +58,6 @@ def index() -> rx.Component: app = rx.App( style=base_style, stylesheets=base_stylesheets, - theme=rx.theme( - appearance="light", has_background=True, radius="large", accent_color="orange" - ), ) app.add_page( index, diff --git a/nba/nba/views/table.py b/nba/nba/views/table.py index e3bf73e..7395d70 100644 --- a/nba/nba/views/table.py +++ b/nba/nba/views/table.py @@ -35,8 +35,8 @@ def _show_player(player: Player, index: int) -> rx.Component: rx.table.cell(player.age), rx.table.cell(player.height), rx.table.cell(player.weight), - rx.table.cell(player.college), - rx.table.cell(player.salary), + rx.table.cell(rx.cond(player.college, player.college, "—")), + rx.table.cell(rx.cond(player.salary, player.salary, "—")), style={"_hover": {"bg": hover_color}, "bg": bg_color}, align="center", ) diff --git a/nba/rxconfig.py b/nba/rxconfig.py index 289c041..8e3ba42 100644 --- a/nba/rxconfig.py +++ b/nba/rxconfig.py @@ -2,5 +2,15 @@ config = rx.Config( app_name="nba", - plugins=[rx.plugins.SitemapPlugin()], + plugins=[ + rx.plugins.SitemapPlugin(), + rx.plugins.RadixThemesPlugin( + theme=rx.theme( + appearance="light", + has_background=True, + radius="large", + accent_color="orange", + ) + ), + ], ) diff --git a/retail_analytics_dashboard/retail_analytics_dashboard/retail_analytics_dashboard.py b/retail_analytics_dashboard/retail_analytics_dashboard/retail_analytics_dashboard.py index 4974cce..c0e3b37 100644 --- a/retail_analytics_dashboard/retail_analytics_dashboard/retail_analytics_dashboard.py +++ b/retail_analytics_dashboard/retail_analytics_dashboard/retail_analytics_dashboard.py @@ -23,5 +23,5 @@ def index() -> rx.Component: ) -app = rx.App(theme=rx.theme(appearance="light")) +app = rx.App() app.add_page(index) diff --git a/retail_analytics_dashboard/rxconfig.py b/retail_analytics_dashboard/rxconfig.py index b425bba..2f2c3a9 100644 --- a/retail_analytics_dashboard/rxconfig.py +++ b/retail_analytics_dashboard/rxconfig.py @@ -2,5 +2,9 @@ config = rx.Config( app_name="retail_analytics_dashboard", - plugins=[rx.plugins.SitemapPlugin(), rx.plugins.TailwindV3Plugin()], + plugins=[ + rx.plugins.SitemapPlugin(), + rx.plugins.TailwindV3Plugin(), + rx.plugins.RadixThemesPlugin(theme=rx.theme(appearance="light")), + ], ) diff --git a/retail_dashboard/retail_dashboard/retail_dashboard.py b/retail_dashboard/retail_dashboard/retail_dashboard.py index 618eff3..b733e09 100644 --- a/retail_dashboard/retail_dashboard/retail_dashboard.py +++ b/retail_dashboard/retail_dashboard/retail_dashboard.py @@ -42,7 +42,6 @@ def index() -> rx.Component: app = rx.App( - theme=rx.theme(appearance="light"), head_components=[ rx.el.link( rel="preconnect", diff --git a/retail_dashboard/rxconfig.py b/retail_dashboard/rxconfig.py index c42bd50..729735a 100644 --- a/retail_dashboard/rxconfig.py +++ b/retail_dashboard/rxconfig.py @@ -2,5 +2,9 @@ config = rx.Config( app_name="retail_dashboard", - plugins=[rx.plugins.SitemapPlugin(), rx.plugins.TailwindV3Plugin()], + plugins=[ + rx.plugins.SitemapPlugin(), + rx.plugins.TailwindV3Plugin(), + rx.plugins.RadixThemesPlugin(theme=rx.theme(appearance="light")), + ], ) diff --git a/retention_dashboard/retention_dashboard/retention_dashboard.py b/retention_dashboard/retention_dashboard/retention_dashboard.py index b59db75..c4de0e5 100644 --- a/retention_dashboard/retention_dashboard/retention_dashboard.py +++ b/retention_dashboard/retention_dashboard/retention_dashboard.py @@ -8,5 +8,5 @@ def index() -> rx.Component: return layout() -app = rx.App(theme=rx.theme(appearance="light")) +app = rx.App() app.add_page(index, route="/") diff --git a/retention_dashboard/rxconfig.py b/retention_dashboard/rxconfig.py index 69ab284..45fa810 100644 --- a/retention_dashboard/rxconfig.py +++ b/retention_dashboard/rxconfig.py @@ -2,5 +2,9 @@ config = rx.Config( app_name="retention_dashboard", - plugins=[rx.plugins.SitemapPlugin(), rx.plugins.TailwindV3Plugin()], + plugins=[ + rx.plugins.SitemapPlugin(), + rx.plugins.TailwindV3Plugin(), + rx.plugins.RadixThemesPlugin(theme=rx.theme(appearance="light")), + ], ) diff --git a/sales/rxconfig.py b/sales/rxconfig.py index 36859c5..73787d4 100644 --- a/sales/rxconfig.py +++ b/sales/rxconfig.py @@ -3,5 +3,15 @@ config = rx.Config( app_name="sales", db_url="sqlite:///reflex.db", - plugins=[rx.plugins.SitemapPlugin()], + plugins=[ + rx.plugins.SitemapPlugin(), + rx.plugins.RadixThemesPlugin( + theme=rx.theme( + appearance="light", + has_background=True, + radius="large", + accent_color="blue", + ) + ), + ], ) diff --git a/sales/sales/sales.py b/sales/sales/sales.py index 90bd449..44ccbb1 100644 --- a/sales/sales/sales.py +++ b/sales/sales/sales.py @@ -25,11 +25,7 @@ def index() -> rx.Component: ) -app = rx.App( - theme=rx.theme( - appearance="light", has_background=True, radius="large", accent_color="blue" - ), -) +app = rx.App() app.add_page( index, on_load=State.load_entries, diff --git a/space_dashboard/rxconfig.py b/space_dashboard/rxconfig.py index d2bd962..ff4ffb5 100644 --- a/space_dashboard/rxconfig.py +++ b/space_dashboard/rxconfig.py @@ -2,5 +2,9 @@ config = rx.Config( app_name="space_dashboard", - plugins=[rx.plugins.SitemapPlugin(), rx.plugins.TailwindV3Plugin()], + plugins=[ + rx.plugins.SitemapPlugin(), + rx.plugins.TailwindV3Plugin(), + rx.plugins.RadixThemesPlugin(theme=rx.theme(appearance="light")), + ], ) diff --git a/space_dashboard/space_dashboard/space_dashboard.py b/space_dashboard/space_dashboard/space_dashboard.py index 1e89468..8cd37c6 100644 --- a/space_dashboard/space_dashboard/space_dashboard.py +++ b/space_dashboard/space_dashboard/space_dashboard.py @@ -45,5 +45,5 @@ def index() -> rx.Component: ) -app = rx.App(theme=rx.theme(appearance="light")) +app = rx.App() app.add_page(index) diff --git a/stock_graph_app/rxconfig.py b/stock_graph_app/rxconfig.py index 358bb65..4d0c40e 100644 --- a/stock_graph_app/rxconfig.py +++ b/stock_graph_app/rxconfig.py @@ -2,5 +2,9 @@ config = rx.Config( app_name="stock_graph_app", - plugins=[rx.plugins.SitemapPlugin(), rx.plugins.TailwindV3Plugin()], + plugins=[ + rx.plugins.SitemapPlugin(), + rx.plugins.TailwindV3Plugin(), + rx.plugins.RadixThemesPlugin(theme=rx.theme(appearance="light")), + ], ) diff --git a/stock_graph_app/stock_graph_app/stock_graph_app.py b/stock_graph_app/stock_graph_app/stock_graph_app.py index 6cbb6b2..a666946 100644 --- a/stock_graph_app/stock_graph_app/stock_graph_app.py +++ b/stock_graph_app/stock_graph_app/stock_graph_app.py @@ -13,7 +13,6 @@ def index() -> rx.Component: app = rx.App( - theme=rx.theme(appearance="light"), head_components=[ rx.el.link( rel="preconnect", diff --git a/stock_market_dashboard/rxconfig.py b/stock_market_dashboard/rxconfig.py index 4d54327..0006f43 100644 --- a/stock_market_dashboard/rxconfig.py +++ b/stock_market_dashboard/rxconfig.py @@ -2,5 +2,9 @@ config = rx.Config( app_name="stock_market_dashboard", - plugins=[rx.plugins.SitemapPlugin(), rx.plugins.TailwindV3Plugin()], + plugins=[ + rx.plugins.SitemapPlugin(), + rx.plugins.TailwindV3Plugin(), + rx.plugins.RadixThemesPlugin(theme=rx.theme(appearance="light")), + ], ) diff --git a/stock_market_dashboard/stock_market_dashboard/stock_market_dashboard.py b/stock_market_dashboard/stock_market_dashboard/stock_market_dashboard.py index f8c49fd..d94f0e7 100644 --- a/stock_market_dashboard/stock_market_dashboard/stock_market_dashboard.py +++ b/stock_market_dashboard/stock_market_dashboard/stock_market_dashboard.py @@ -25,7 +25,6 @@ def index() -> rx.Component: app = rx.App( - theme=rx.theme(appearance="light"), stylesheets=["/styles.css"], ) app.add_page(index, route="/") diff --git a/table_dashboard/rxconfig.py b/table_dashboard/rxconfig.py index 1752434..3b23a82 100644 --- a/table_dashboard/rxconfig.py +++ b/table_dashboard/rxconfig.py @@ -2,5 +2,9 @@ config = rx.Config( app_name="table_dashboard", - plugins=[rx.plugins.SitemapPlugin(), rx.plugins.TailwindV3Plugin()], + plugins=[ + rx.plugins.SitemapPlugin(), + rx.plugins.TailwindV3Plugin(), + rx.plugins.RadixThemesPlugin(theme=rx.theme(appearance="light")), + ], ) diff --git a/table_dashboard/table_dashboard/table_dashboard.py b/table_dashboard/table_dashboard/table_dashboard.py index 9549476..0fbd4ef 100644 --- a/table_dashboard/table_dashboard/table_dashboard.py +++ b/table_dashboard/table_dashboard/table_dashboard.py @@ -32,5 +32,5 @@ def index() -> rx.Component: ) -app = rx.App(theme=rx.theme(appearance="light")) +app = rx.App() app.add_page(index) diff --git a/text_annotation_app/rxconfig.py b/text_annotation_app/rxconfig.py index 8226dfe..e9dd8d7 100644 --- a/text_annotation_app/rxconfig.py +++ b/text_annotation_app/rxconfig.py @@ -2,5 +2,9 @@ config = rx.Config( app_name="text_annotation_app", - plugins=[rx.plugins.SitemapPlugin(), rx.plugins.TailwindV3Plugin()], + plugins=[ + rx.plugins.SitemapPlugin(), + rx.plugins.TailwindV3Plugin(), + rx.plugins.RadixThemesPlugin(theme=rx.theme(appearance="light")), + ], ) diff --git a/text_annotation_app/text_annotation_app/text_annotation_app.py b/text_annotation_app/text_annotation_app/text_annotation_app.py index f70433e..aa2a3f8 100644 --- a/text_annotation_app/text_annotation_app/text_annotation_app.py +++ b/text_annotation_app/text_annotation_app/text_annotation_app.py @@ -13,5 +13,5 @@ def index() -> rx.Component: ) -app = rx.App(theme=rx.theme(appearance="light")) +app = rx.App() app.add_page(index, route="/") From 1ce184a1a94d0b5f366c471c86d3e93e9e6a8a87 Mon Sep 17 00:00:00 2001 From: Farhan Ali Raza Date: Tue, 2 Jun 2026 23:26:02 +0500 Subject: [PATCH 5/5] fix: migrate Customer models to SQLModel and add RadixThemesPlugin Use SQLModel directly with explicit primary-key fields, replace get_fields()/set() with model_fields and setattr, and register RadixThemesPlugin across template rxconfigs. Co-Authored-By: Claude Opus 4.8 (1M context) --- api_admin_panel/rxconfig.py | 5 ++++- ci_template/rxconfig.py | 5 ++++- customer_data_app/customer_data/backend/backend.py | 10 ++++++---- dashboard/rxconfig.py | 5 ++++- sales/sales/backend/backend.py | 6 ++++-- sales/sales/backend/models.py | 5 +++-- 6 files changed, 25 insertions(+), 11 deletions(-) diff --git a/api_admin_panel/rxconfig.py b/api_admin_panel/rxconfig.py index dcf807f..5f619f3 100644 --- a/api_admin_panel/rxconfig.py +++ b/api_admin_panel/rxconfig.py @@ -2,5 +2,8 @@ config = rx.Config( app_name="api_admin_panel", - plugins=[rx.plugins.SitemapPlugin()], + plugins=[ + rx.plugins.SitemapPlugin(), + rx.plugins.RadixThemesPlugin(), + ], ) diff --git a/ci_template/rxconfig.py b/ci_template/rxconfig.py index 870b901..a025ee6 100644 --- a/ci_template/rxconfig.py +++ b/ci_template/rxconfig.py @@ -2,5 +2,8 @@ config = rx.Config( app_name="cijob", - plugins=[rx.plugins.SitemapPlugin()], + plugins=[ + rx.plugins.SitemapPlugin(), + rx.plugins.RadixThemesPlugin(), + ], ) diff --git a/customer_data_app/customer_data/backend/backend.py b/customer_data_app/customer_data/backend/backend.py index 118f859..5eef613 100644 --- a/customer_data_app/customer_data/backend/backend.py +++ b/customer_data_app/customer_data/backend/backend.py @@ -3,7 +3,7 @@ from typing import Union import reflex as rx -from sqlmodel import String, asc, cast, desc, func, or_, select +from sqlmodel import Field, SQLModel, String, asc, cast, desc, func, or_, select def _get_percentage_change( @@ -19,9 +19,10 @@ def _get_percentage_change( return percentage_change -class Customer(rx.Model, table=True): +class Customer(SQLModel, table=True): """The customer model.""" + id: int | None = Field(default=None, primary_key=True) name: str email: str phone: str @@ -62,7 +63,7 @@ def load_entries(self) -> list[Customer]: or_( *[ getattr(Customer, field).ilike(search_value) - for field in Customer.get_fields() + for field in Customer.model_fields if field not in ["id", "payments"] ], # ensures that payments is cast to a string before applying the ilike operator @@ -176,7 +177,8 @@ def update_customer_to_db(self, form_data: dict): select(Customer).where(Customer.id == self.current_user.id) ).first() form_data.pop("id", None) - customer.set(**form_data) + for field, value in form_data.items(): + setattr(customer, field, value) session.add(customer) session.commit() self.load_entries() diff --git a/dashboard/rxconfig.py b/dashboard/rxconfig.py index bc07034..e2cc8b1 100644 --- a/dashboard/rxconfig.py +++ b/dashboard/rxconfig.py @@ -2,5 +2,8 @@ config = rx.Config( app_name="dashboard", - plugins=[rx.plugins.SitemapPlugin()], + plugins=[ + rx.plugins.SitemapPlugin(), + rx.plugins.RadixThemesPlugin(), + ], ) diff --git a/sales/sales/backend/backend.py b/sales/sales/backend/backend.py index 7a27a52..df6ebde 100644 --- a/sales/sales/backend/backend.py +++ b/sales/sales/backend/backend.py @@ -91,7 +91,7 @@ def load_entries(self) -> list[Customer]: or_( *[ getattr(Customer, field).ilike(search_value) - for field in Customer.get_fields() + for field in Customer.model_fields ], ) ) @@ -147,7 +147,9 @@ def update_customer_to_db(self, form_data: dict): customer = session.exec( select(Customer).where(Customer.id == self.current_user.id) ).first() - customer.set(**form_data) + form_data.pop("id", None) + for field, value in form_data.items(): + setattr(customer, field, value) session.commit() session.refresh(customer) self.current_user = customer diff --git a/sales/sales/backend/models.py b/sales/sales/backend/models.py index feb5a60..5839874 100644 --- a/sales/sales/backend/models.py +++ b/sales/sales/backend/models.py @@ -1,9 +1,10 @@ -import reflex as rx +from sqlmodel import Field, SQLModel -class Customer(rx.Model, table=True): # type: ignore +class Customer(SQLModel, table=True): # type: ignore """The customer model.""" + id: int | None = Field(default=None, primary_key=True) customer_name: str email: str age: int