From 41ed9f0514cc99d5b105e1341f2fc71490c89330 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 2 Jan 2025 14:15:38 -0800 Subject: [PATCH 01/44] Move _create_event_chain to EventChain.create (#4557) The ability to convert `EventType` arguments into `EventChain` is crucial for wrapping JS libraries that need to pass event handlers via hooks or other non-component centric interfaces. Improve typing such that wrapped components accepting `EventType` arguments can be directly converted to `EventChain` / `EventChainVar` to be rendered as JS code. --- reflex/components/component.py | 100 ++++++--------------------------- reflex/event.py | 90 +++++++++++++++++++++++++++++ 2 files changed, 107 insertions(+), 83 deletions(-) diff --git a/reflex/components/component.py b/reflex/components/component.py index 34800ab6e..c1d4cbc80 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -23,6 +23,8 @@ from typing import ( Union, ) +from typing_extensions import deprecated + import reflex.state from reflex.base import Base from reflex.compiler.templates import STATEFUL_COMPONENT @@ -43,17 +45,13 @@ from reflex.constants.state import FRONTEND_EVENT_STATE from reflex.event import ( EventCallback, EventChain, - EventChainVar, EventHandler, EventSpec, EventVar, - call_event_fn, - call_event_handler, - get_handler_args, no_args_event_spec, ) from reflex.style import Style, format_as_emotion -from reflex.utils import format, imports, types +from reflex.utils import console, format, imports, types from reflex.utils.imports import ( ImmutableParsedImportDict, ImportDict, @@ -493,8 +491,7 @@ class Component(BaseComponent, ABC): ) # Check if the key is an event trigger. if key in component_specific_triggers: - # Temporarily disable full control for event triggers. - kwargs["event_triggers"][key] = self._create_event_chain( + kwargs["event_triggers"][key] = EventChain.create( value=value, # type: ignore args_spec=component_specific_triggers[key], key=key, @@ -548,6 +545,7 @@ class Component(BaseComponent, ABC): # Construct the component. super().__init__(*args, **kwargs) + @deprecated("Use rx.EventChain.create instead.") def _create_event_chain( self, args_spec: types.ArgsSpec | Sequence[types.ArgsSpec], @@ -569,82 +567,18 @@ class Component(BaseComponent, ABC): Returns: The event chain. - - Raises: - ValueError: If the value is not a valid event chain. """ - # If it's an event chain var, return it. - if isinstance(value, Var): - if isinstance(value, EventChainVar): - return value - elif isinstance(value, EventVar): - value = [value] - elif issubclass(value._var_type, (EventChain, EventSpec)): - return self._create_event_chain(args_spec, value.guess_type(), key=key) - else: - raise ValueError( - f"Invalid event chain: {value!s} of type {value._var_type}" - ) - elif isinstance(value, EventChain): - # Trust that the caller knows what they're doing passing an EventChain directly - return value - - # If the input is a single event handler, wrap it in a list. - if isinstance(value, (EventHandler, EventSpec)): - value = [value] - - # If the input is a list of event handlers, create an event chain. - if isinstance(value, List): - events: List[Union[EventSpec, EventVar]] = [] - for v in value: - if isinstance(v, (EventHandler, EventSpec)): - # Call the event handler to get the event. - events.append(call_event_handler(v, args_spec, key=key)) - elif isinstance(v, Callable): - # Call the lambda to get the event chain. - result = call_event_fn(v, args_spec, key=key) - if isinstance(result, Var): - raise ValueError( - f"Invalid event chain: {v}. Cannot use a Var-returning " - "lambda inside an EventChain list." - ) - events.extend(result) - elif isinstance(v, EventVar): - events.append(v) - else: - raise ValueError(f"Invalid event: {v}") - - # If the input is a callable, create an event chain. - elif isinstance(value, Callable): - result = call_event_fn(value, args_spec, key=key) - if isinstance(result, Var): - # Recursively call this function if the lambda returned an EventChain Var. - return self._create_event_chain(args_spec, result, key=key) - events = [*result] - - # Otherwise, raise an error. - else: - raise ValueError(f"Invalid event chain: {value}") - - # Add args to the event specs if necessary. - events = [ - (e.with_args(get_handler_args(e)) if isinstance(e, EventSpec) else e) - for e in events - ] - - # Return the event chain. - if isinstance(args_spec, Var): - return EventChain( - events=events, - args_spec=None, - event_actions={}, - ) - else: - return EventChain( - events=events, - args_spec=args_spec, - event_actions={}, - ) + console.deprecate( + "Component._create_event_chain", + "Use rx.EventChain.create instead.", + deprecation_version="0.6.8", + removal_version="0.7.0", + ) + return EventChain.create( + value=value, # type: ignore + args_spec=args_spec, + key=key, + ) def get_event_triggers( self, @@ -1737,7 +1671,7 @@ class CustomComponent(Component): # Handle event chains. if types._issubclass(type_, EventChain): - value = self._create_event_chain( + value = EventChain.create( value=value, args_spec=event_triggers_in_component_declaration.get( key, no_args_event_spec diff --git a/reflex/event.py b/reflex/event.py index e4ca55c70..0a4a01837 100644 --- a/reflex/event.py +++ b/reflex/event.py @@ -431,6 +431,96 @@ class EventChain(EventActionsMixin): invocation: Optional[Var] = dataclasses.field(default=None) + @classmethod + def create( + cls, + value: EventType, + args_spec: ArgsSpec | Sequence[ArgsSpec], + key: Optional[str] = None, + ) -> Union[EventChain, Var]: + """Create an event chain from a variety of input types. + + Args: + value: The value to create the event chain from. + args_spec: The args_spec of the event trigger being bound. + key: The key of the event trigger being bound. + + Returns: + The event chain. + + Raises: + ValueError: If the value is not a valid event chain. + """ + # If it's an event chain var, return it. + if isinstance(value, Var): + if isinstance(value, EventChainVar): + return value + elif isinstance(value, EventVar): + value = [value] + elif issubclass(value._var_type, (EventChain, EventSpec)): + return cls.create( + value=value.guess_type(), + args_spec=args_spec, + key=key, + ) + else: + raise ValueError( + f"Invalid event chain: {value!s} of type {value._var_type}" + ) + elif isinstance(value, EventChain): + # Trust that the caller knows what they're doing passing an EventChain directly + return value + + # If the input is a single event handler, wrap it in a list. + if isinstance(value, (EventHandler, EventSpec)): + value = [value] + + # If the input is a list of event handlers, create an event chain. + if isinstance(value, List): + events: List[Union[EventSpec, EventVar]] = [] + for v in value: + if isinstance(v, (EventHandler, EventSpec)): + # Call the event handler to get the event. + events.append(call_event_handler(v, args_spec, key=key)) + elif isinstance(v, Callable): + # Call the lambda to get the event chain. + result = call_event_fn(v, args_spec, key=key) + if isinstance(result, Var): + raise ValueError( + f"Invalid event chain: {v}. Cannot use a Var-returning " + "lambda inside an EventChain list." + ) + events.extend(result) + elif isinstance(v, EventVar): + events.append(v) + else: + raise ValueError(f"Invalid event: {v}") + + # If the input is a callable, create an event chain. + elif isinstance(value, Callable): + result = call_event_fn(value, args_spec, key=key) + if isinstance(result, Var): + # Recursively call this function if the lambda returned an EventChain Var. + return cls.create(value=result, args_spec=args_spec, key=key) + events = [*result] + + # Otherwise, raise an error. + else: + raise ValueError(f"Invalid event chain: {value}") + + # Add args to the event specs if necessary. + events = [ + (e.with_args(get_handler_args(e)) if isinstance(e, EventSpec) else e) + for e in events + ] + + # Return the event chain. + return cls( + events=events, + args_spec=args_spec, + event_actions={}, + ) + @dataclasses.dataclass( init=True, From 12eaf08c889c0caf88a430eba63c03aee12f8cdb Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 2 Jan 2025 14:15:59 -0800 Subject: [PATCH 02/44] Add `expire_on_commit=False` for async sessions (#4582) --- reflex/model.py | 1 + 1 file changed, 1 insertion(+) diff --git a/reflex/model.py b/reflex/model.py index de63589fc..295159de0 100644 --- a/reflex/model.py +++ b/reflex/model.py @@ -533,6 +533,7 @@ def asession(url: str | None = None) -> AsyncSession: _AsyncSessionLocal[url] = sqlalchemy.ext.asyncio.async_sessionmaker( bind=get_async_engine(url), class_=AsyncSession, + expire_on_commit=False, autocommit=False, autoflush=False, ) From 97fb157b2580d3f2d0ab028a97ff7edd3f3f908c Mon Sep 17 00:00:00 2001 From: Ryan <15974789+5quinque@users.noreply.github.com> Date: Fri, 3 Jan 2025 20:50:04 +0000 Subject: [PATCH 03/44] Add `endswith` method to String class (#4577) Co-authored-by: ryan <> --- reflex/vars/sequence.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/reflex/vars/sequence.py b/reflex/vars/sequence.py index 476c1e32c..5864e70b9 100644 --- a/reflex/vars/sequence.py +++ b/reflex/vars/sequence.py @@ -271,6 +271,25 @@ class StringVar(Var[STRING_TYPE], python_types=str): raise_unsupported_operand_types("startswith", (type(self), type(prefix))) return string_starts_with_operation(self, prefix) + @overload + def endswith(self, suffix: StringVar | str) -> BooleanVar: ... + + @overload + def endswith(self, suffix: NoReturn) -> NoReturn: ... + + def endswith(self, suffix: Any) -> BooleanVar: + """Check if the string ends with a suffix. + + Args: + suffix: The suffix. + + Returns: + The string ends with operation. + """ + if not isinstance(suffix, (StringVar, str)): + raise_unsupported_operand_types("endswith", (type(self), type(suffix))) + return string_ends_with_operation(self, suffix) + @overload def __lt__(self, other: StringVar | str) -> BooleanVar: ... @@ -501,6 +520,24 @@ def string_starts_with_operation( ) +@var_operation +def string_ends_with_operation( + full_string: StringVar[Any], suffix: StringVar[Any] | str +): + """Check if a string ends with a suffix. + + Args: + full_string: The full string. + suffix: The suffix. + + Returns: + Whether the string ends with the suffix. + """ + return var_operation_return( + js_expression=f"{full_string}.endsWith({suffix})", var_type=bool + ) + + @var_operation def string_item_operation(string: StringVar[Any], index: NumberVar | int): """Get an item from a string. From 53f09756b6fb03d759bee2aecc55ceeec08e9ff1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= Date: Fri, 3 Jan 2025 12:51:02 -0800 Subject: [PATCH 04/44] autodetect print/breakpoints (#4581) * catch stray breakpoints and prints * autodetect debug prints and breakpoints in main code * readd hacky print in pyi_generator? --- benchmarks/benchmark_package_size.py | 2 +- pyproject.toml | 4 ++-- reflex/testing.py | 6 +++--- reflex/utils/pyi_generator.py | 2 +- scripts/wait_for_listening_port.py | 6 +++--- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/benchmarks/benchmark_package_size.py b/benchmarks/benchmark_package_size.py index 778b52769..6a0c37821 100644 --- a/benchmarks/benchmark_package_size.py +++ b/benchmarks/benchmark_package_size.py @@ -21,7 +21,7 @@ def get_package_size(venv_path: Path, os_name): ValueError: when venv does not exist or python version is None. """ python_version = get_python_version(venv_path, os_name) - print("Python version:", python_version) + print("Python version:", python_version) # noqa: T201 if python_version is None: raise ValueError("Error: Failed to determine Python version.") diff --git a/pyproject.toml b/pyproject.toml index 8fd1886f0..21bbec91f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -87,13 +87,13 @@ build-backend = "poetry.core.masonry.api" target-version = "py39" output-format = "concise" lint.isort.split-on-trailing-comma = false -lint.select = ["B", "C4", "D", "E", "ERA", "F", "FURB", "I", "PERF", "PTH", "RUF", "SIM", "W"] +lint.select = ["B", "C4", "D", "E", "ERA", "F", "FURB", "I", "PERF", "PTH", "RUF", "SIM", "T", "W"] lint.ignore = ["B008", "D205", "E501", "F403", "SIM115", "RUF006", "RUF012"] lint.pydocstyle.convention = "google" [tool.ruff.lint.per-file-ignores] "__init__.py" = ["F401"] -"tests/*.py" = ["D100", "D103", "D104", "B018", "PERF"] +"tests/*.py" = ["D100", "D103", "D104", "B018", "PERF", "T"] "reflex/.templates/*.py" = ["D100", "D103", "D104"] "*.pyi" = ["D301", "D415", "D417", "D418", "E742"] "*/blank.py" = ["I001"] diff --git a/reflex/testing.py b/reflex/testing.py index ca31054b3..b3dedf398 100644 --- a/reflex/testing.py +++ b/reflex/testing.py @@ -52,6 +52,7 @@ from reflex.state import ( StateManagerRedis, reload_state_module, ) +from reflex.utils import console try: from selenium import webdriver # pyright: ignore [reportMissingImports] @@ -385,7 +386,7 @@ class AppHarness: ) if not line: break - print(line) # for pytest diagnosis + print(line) # for pytest diagnosis #noqa: T201 m = re.search(reflex.constants.Next.FRONTEND_LISTENING_REGEX, line) if m is not None: self.frontend_url = m.group(1) @@ -403,11 +404,10 @@ class AppHarness: ) # catch I/O operation on closed file. except ValueError as e: - print(e) + console.error(str(e)) break if not line: break - print(line) self.frontend_output_thread = threading.Thread(target=consume_frontend_output) self.frontend_output_thread.start() diff --git a/reflex/utils/pyi_generator.py b/reflex/utils/pyi_generator.py index 6a4bd63f8..152c06949 100644 --- a/reflex/utils/pyi_generator.py +++ b/reflex/utils/pyi_generator.py @@ -1202,4 +1202,4 @@ class PyiGenerator: or "Var[Template]" in line ): line = line.rstrip() + " # type: ignore\n" - print(line, end="") + print(line, end="") # noqa: T201 diff --git a/scripts/wait_for_listening_port.py b/scripts/wait_for_listening_port.py index 857ee7c6d..43581f0bc 100644 --- a/scripts/wait_for_listening_port.py +++ b/scripts/wait_for_listening_port.py @@ -25,7 +25,7 @@ def _pid_exists(pid): def _wait_for_port(port, server_pid, timeout) -> Tuple[bool, str]: start = time.time() - print(f"Waiting for up to {timeout} seconds for port {port} to start listening.") + print(f"Waiting for up to {timeout} seconds for port {port} to start listening.") # noqa: T201 while True: if not _pid_exists(server_pid): return False, f"Server PID {server_pid} is not running." @@ -56,9 +56,9 @@ def main(): for f in as_completed(futures): ok, msg = f.result() if ok: - print(f"OK: {msg}") + print(f"OK: {msg}") # noqa: T201 else: - print(f"FAIL: {msg}") + print(f"FAIL: {msg}") # noqa: T201 exit(1) From 72a60f074b5ca5721d34e5a7a99e95e04d89b602 Mon Sep 17 00:00:00 2001 From: JonZeolla Date: Fri, 3 Jan 2025 15:55:51 -0500 Subject: [PATCH 05/44] chore: update sonner (toast) (#4572) --- reflex/components/sonner/toast.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/reflex/components/sonner/toast.py b/reflex/components/sonner/toast.py index 836c19bf9..b978409ab 100644 --- a/reflex/components/sonner/toast.py +++ b/reflex/components/sonner/toast.py @@ -167,7 +167,7 @@ class ToastProps(PropsBase, NoExtrasAllowedProps): class Toaster(Component): """A Toaster Component for displaying toast notifications.""" - library: str = "sonner@1.5.0" + library: str = "sonner@1.7.1" tag = "Toaster" From 0d9b2c75e4b5f6dab30f28a53f4cdae46d98d802 Mon Sep 17 00:00:00 2001 From: celsius narhwal Date: Fri, 3 Jan 2025 15:59:05 -0500 Subject: [PATCH 06/44] Upgrade lucide-react to version 0.469.0 (#4571) --- reflex/components/lucide/icon.py | 161 ++++++++++++++++++++++++++---- reflex/components/lucide/icon.pyi | 159 +++++++++++++++++++++++++---- 2 files changed, 283 insertions(+), 37 deletions(-) diff --git a/reflex/components/lucide/icon.py b/reflex/components/lucide/icon.py index b32fb8de3..6b7692643 100644 --- a/reflex/components/lucide/icon.py +++ b/reflex/components/lucide/icon.py @@ -8,7 +8,7 @@ from reflex.vars.base import Var class LucideIconComponent(Component): """Lucide Icon Component.""" - library = "lucide-react@0.359.0" + library = "lucide-react@0.469.0" class Icon(LucideIconComponent): @@ -106,6 +106,7 @@ LUCIDE_ICON_LIST = [ "ambulance", "ampersand", "ampersands", + "amphora", "anchor", "angry", "annoyed", @@ -193,6 +194,7 @@ LUCIDE_ICON_LIST = [ "baggage_claim", "ban", "banana", + "bandage", "banknote", "bar_chart", "bar_chart_2", @@ -230,8 +232,10 @@ LUCIDE_ICON_LIST = [ "between_horizontal_start", "between_vertical_end", "between_vertical_start", + "biceps_flexed", "bike", "binary", + "binoculars", "biohazard", "bird", "bitcoin", @@ -278,6 +282,7 @@ LUCIDE_ICON_LIST = [ "boom_box", "bot", "bot_message_square", + "bot_off", "box", "box_select", "boxes", @@ -289,6 +294,7 @@ LUCIDE_ICON_LIST = [ "brick_wall", "briefcase", "briefcase_business", + "briefcase_conveyor_belt", "briefcase_medical", "bring_to_front", "brush", @@ -305,9 +311,13 @@ LUCIDE_ICON_LIST = [ "cake_slice", "calculator", "calendar", + "calendar_1", + "calendar_arrow_down", + "calendar_arrow_up", "calendar_check", "calendar_check_2", "calendar_clock", + "calendar_cog", "calendar_days", "calendar_fold", "calendar_heart", @@ -318,6 +328,7 @@ LUCIDE_ICON_LIST = [ "calendar_plus_2", "calendar_range", "calendar_search", + "calendar_sync", "calendar_x", "calendar_x_2", "camera", @@ -342,6 +353,29 @@ LUCIDE_ICON_LIST = [ "castle", "cat", "cctv", + "chart_area", + "chart_bar", + "chart_bar_big", + "chart_bar_decreasing", + "chart_bar_increasing", + "chart_bar_stacked", + "chart_candlestick", + "chart_column", + "chart_column_big", + "chart_column_decreasing", + "chart_column_increasing", + "chart_column_stacked", + "chart_gantt", + "chart_line", + "chart_network", + "chart_no_axes_column", + "chart_no_axes_column_decreasing", + "chart_no_axes_column_increasing", + "chart_no_axes_combined", + "chart_no_axes_gantt", + "chart_pie", + "chart_scatter", + "chart_spline", "check", "check_check", "chef_hat", @@ -356,6 +390,7 @@ LUCIDE_ICON_LIST = [ "chevrons_down_up", "chevrons_left", "chevrons_left_right", + "chevrons_left_right_ellipsis", "chevrons_right", "chevrons_right_left", "chevrons_up", @@ -374,8 +409,8 @@ LUCIDE_ICON_LIST = [ "circle_arrow_out_up_right", "circle_arrow_right", "circle_arrow_up", - "circle_check_big", "circle_check", + "circle_check_big", "circle_chevron_down", "circle_chevron_left", "circle_chevron_right", @@ -387,13 +422,14 @@ LUCIDE_ICON_LIST = [ "circle_dot_dashed", "circle_ellipsis", "circle_equal", + "circle_fading_arrow_up", "circle_fading_plus", "circle_gauge", "circle_help", "circle_minus", "circle_off", - "circle_parking_off", "circle_parking", + "circle_parking_off", "circle_pause", "circle_percent", "circle_play", @@ -432,7 +468,11 @@ LUCIDE_ICON_LIST = [ "clock_7", "clock_8", "clock_9", + "clock_alert", + "clock_arrow_down", + "clock_arrow_up", "cloud", + "cloud_alert", "cloud_cog", "cloud_download", "cloud_drizzle", @@ -503,6 +543,7 @@ LUCIDE_ICON_LIST = [ "cup_soda", "currency", "cylinder", + "dam", "database", "database_backup", "database_zap", @@ -510,7 +551,9 @@ LUCIDE_ICON_LIST = [ "dessert", "diameter", "diamond", + "diamond_minus", "diamond_percent", + "diamond_plus", "dice_1", "dice_2", "dice_3", @@ -539,6 +582,7 @@ LUCIDE_ICON_LIST = [ "dribbble", "drill", "droplet", + "droplet_off", "droplets", "drum", "drumstick", @@ -554,12 +598,15 @@ LUCIDE_ICON_LIST = [ "ellipsis", "ellipsis_vertical", "equal", + "equal_approximately", "equal_not", "eraser", + "ethernet_port", "euro", "expand", "external_link", "eye", + "eye_closed", "eye_off", "facebook", "factory", @@ -579,6 +626,10 @@ LUCIDE_ICON_LIST = [ "file_bar_chart", "file_bar_chart_2", "file_box", + "file_chart_column", + "file_chart_column_increasing", + "file_chart_line", + "file_chart_pie", "file_check", "file_check_2", "file_clock", @@ -620,6 +671,7 @@ LUCIDE_ICON_LIST = [ "file_type", "file_type_2", "file_up", + "file_user", "file_video", "file_video_2", "file_volume", @@ -661,6 +713,7 @@ LUCIDE_ICON_LIST = [ "folder_check", "folder_clock", "folder_closed", + "folder_code", "folder_cog", "folder_dot", "folder_down", @@ -733,7 +786,12 @@ LUCIDE_ICON_LIST = [ "graduation_cap", "grape", "grid_2x2", + "grid_2x_2", + "grid_2x_2_check", + "grid_2x_2_plus", + "grid_2x_2_x", "grid_3x3", + "grid_3x_3", "grip", "grip_horizontal", "grip_vertical", @@ -762,6 +820,7 @@ LUCIDE_ICON_LIST = [ "heading_4", "heading_5", "heading_6", + "headphone_off", "headphones", "headset", "heart", @@ -779,14 +838,20 @@ LUCIDE_ICON_LIST = [ "hospital", "hotel", "hourglass", + "house", + "house_plug", + "house_plus", "ice_cream_bowl", "ice_cream_cone", + "id_card", "image", "image_down", "image_minus", "image_off", + "image_play", "image_plus", "image_up", + "image_upscale", "images", "import", "inbox", @@ -808,6 +873,7 @@ LUCIDE_ICON_LIST = [ "key_square", "keyboard", "keyboard_music", + "keyboard_off", "lamp", "lamp_ceiling", "lamp_desk", @@ -817,8 +883,9 @@ LUCIDE_ICON_LIST = [ "land_plot", "landmark", "languages", - "laptop_minimal", "laptop", + "laptop_minimal", + "laptop_minimal_check", "lasso", "lasso_select", "laugh", @@ -833,6 +900,8 @@ LUCIDE_ICON_LIST = [ "layout_template", "leaf", "leafy_green", + "lectern", + "letter_text", "library", "library_big", "life_buoy", @@ -845,10 +914,12 @@ LUCIDE_ICON_LIST = [ "link_2_off", "linkedin", "list", + "list_check", "list_checks", "list_collapse", "list_end", "list_filter", + "list_filter_plus", "list_minus", "list_music", "list_ordered", @@ -861,15 +932,17 @@ LUCIDE_ICON_LIST = [ "list_x", "loader", "loader_circle", + "loader_pinwheel", "locate", "locate_fixed", "locate_off", "lock", - "lock_keyhole_open", "lock_keyhole", + "lock_keyhole_open", "lock_open", "log_in", "log_out", + "logs", "lollipop", "luggage", "magnet", @@ -886,7 +959,16 @@ LUCIDE_ICON_LIST = [ "mails", "map", "map_pin", + "map_pin_check", + "map_pin_check_inside", + "map_pin_house", + "map_pin_minus", + "map_pin_minus_inside", "map_pin_off", + "map_pin_plus", + "map_pin_plus_inside", + "map_pin_x", + "map_pin_x_inside", "map_pinned", "martini", "maximize", @@ -915,6 +997,7 @@ LUCIDE_ICON_LIST = [ "message_square_diff", "message_square_dot", "message_square_heart", + "message_square_lock", "message_square_more", "message_square_off", "message_square_plus", @@ -926,8 +1009,9 @@ LUCIDE_ICON_LIST = [ "message_square_x", "messages_square", "mic", - "mic_vocal", "mic_off", + "mic_vocal", + "microchip", "microscope", "microwave", "milestone", @@ -938,6 +1022,7 @@ LUCIDE_ICON_LIST = [ "minus", "monitor", "monitor_check", + "monitor_cog", "monitor_dot", "monitor_down", "monitor_off", @@ -953,8 +1038,10 @@ LUCIDE_ICON_LIST = [ "mountain", "mountain_snow", "mouse", + "mouse_off", "mouse_pointer", "mouse_pointer_2", + "mouse_pointer_ban", "mouse_pointer_click", "move", "move_3d", @@ -991,10 +1078,13 @@ LUCIDE_ICON_LIST = [ "nut_off", "octagon", "octagon_alert", + "octagon_minus", "octagon_pause", "octagon_x", + "omega", "option", "orbit", + "origami", "package", "package_2", "package_check", @@ -1007,6 +1097,7 @@ LUCIDE_ICON_LIST = [ "paint_roller", "paintbrush", "paintbrush_2", + "paintbrush_vertical", "palette", "panel_bottom", "panel_bottom_close", @@ -1036,13 +1127,16 @@ LUCIDE_ICON_LIST = [ "pc_case", "pen", "pen_line", + "pen_off", "pen_tool", "pencil", "pencil_line", + "pencil_off", "pencil_ruler", "pentagon", "percent", "person_standing", + "philippine_peso", "phone", "phone_call", "phone_forwarded", @@ -1058,7 +1152,10 @@ LUCIDE_ICON_LIST = [ "pie_chart", "piggy_bank", "pilcrow", + "pilcrow_left", + "pilcrow_right", "pill", + "pill_bottle", "pin", "pin_off", "pipette", @@ -1084,6 +1181,7 @@ LUCIDE_ICON_LIST = [ "power_off", "presentation", "printer", + "printer_check", "projector", "proportions", "puzzle", @@ -1158,6 +1256,7 @@ LUCIDE_ICON_LIST = [ "satellite_dish", "save", "save_all", + "save_off", "scale", "scale_3d", "scaling", @@ -1165,7 +1264,9 @@ LUCIDE_ICON_LIST = [ "scan_barcode", "scan_eye", "scan_face", + "scan_heart", "scan_line", + "scan_qr_code", "scan_search", "scan_text", "scatter_chart", @@ -1181,6 +1282,7 @@ LUCIDE_ICON_LIST = [ "search_code", "search_slash", "search_x", + "section", "send", "send_horizontal", "send_to_back", @@ -1225,6 +1327,7 @@ LUCIDE_ICON_LIST = [ "signal_low", "signal_medium", "signal_zero", + "signature", "signpost", "signpost_big", "siren", @@ -1234,8 +1337,8 @@ LUCIDE_ICON_LIST = [ "slack", "slash", "slice", - "sliders_vertical", "sliders_horizontal", + "sliders_vertical", "smartphone", "smartphone_charging", "smartphone_nfc", @@ -1259,29 +1362,31 @@ LUCIDE_ICON_LIST = [ "sprout", "square", "square_activity", + "square_arrow_down", "square_arrow_down_left", "square_arrow_down_right", - "square_arrow_down", "square_arrow_left", "square_arrow_out_down_left", "square_arrow_out_down_right", "square_arrow_out_up_left", "square_arrow_out_up_right", "square_arrow_right", + "square_arrow_up", "square_arrow_up_left", "square_arrow_up_right", - "square_arrow_up", "square_asterisk", "square_bottom_dashed_scissors", - "square_check_big", + "square_chart_gantt", "square_check", + "square_check_big", "square_chevron_down", "square_chevron_left", "square_chevron_right", "square_chevron_up", "square_code", - "square_dashed_bottom_code", + "square_dashed", "square_dashed_bottom", + "square_dashed_bottom_code", "square_dashed_kanban", "square_dashed_mouse_pointer", "square_divide", @@ -1295,8 +1400,8 @@ LUCIDE_ICON_LIST = [ "square_menu", "square_minus", "square_mouse_pointer", - "square_parking_off", "square_parking", + "square_parking_off", "square_pen", "square_percent", "square_pi", @@ -1310,10 +1415,11 @@ LUCIDE_ICON_LIST = [ "square_slash", "square_split_horizontal", "square_split_vertical", + "square_square", "square_stack", "square_terminal", - "square_user_round", "square_user", + "square_user_round", "square_x", "squircle", "squirrel", @@ -1350,6 +1456,7 @@ LUCIDE_ICON_LIST = [ "table_cells_merge", "table_cells_split", "table_columns_split", + "table_of_contents", "table_properties", "table_rows_split", "tablet", @@ -1365,11 +1472,11 @@ LUCIDE_ICON_LIST = [ "tangent", "target", "telescope", + "tent", "tent_tree", "terminal", - "test_tube_diagonal", "test_tube", - "tent", + "test_tube_diagonal", "test_tubes", "text", "text_cursor", @@ -1390,11 +1497,14 @@ LUCIDE_ICON_LIST = [ "ticket_plus", "ticket_slash", "ticket_x", + "tickets", + "tickets_plane", "timer", "timer_off", "timer_reset", "toggle_left", "toggle_right", + "toilet", "tornado", "torus", "touchpad", @@ -1416,17 +1526,21 @@ LUCIDE_ICON_LIST = [ "trello", "trending_down", "trending_up", + "trending_up_down", "triangle", - "triangle_right", "triangle_alert", + "triangle_right", "trophy", "truck", "turtle", "tv", "tv_2", + "tv_minimal", + "tv_minimal_play", "twitch", "twitter", "type", + "type_outline", "umbrella", "umbrella_off", "underline", @@ -1437,8 +1551,8 @@ LUCIDE_ICON_LIST = [ "unfold_vertical", "ungroup", "university", - "unlink_2", "unlink", + "unlink_2", "unplug", "upload", "usb", @@ -1446,11 +1560,13 @@ LUCIDE_ICON_LIST = [ "user_check", "user_cog", "user_minus", + "user_pen", "user_plus", "user_round", "user_round_check", "user_round_cog", "user_round_minus", + "user_round_pen", "user_round_plus", "user_round_search", "user_round_x", @@ -1472,14 +1588,16 @@ LUCIDE_ICON_LIST = [ "videotape", "view", "voicemail", + "volleyball", "volume", "volume_1", "volume_2", + "volume_off", "volume_x", "vote", "wallet", - "wallet_minimal", "wallet_cards", + "wallet_minimal", "wallpaper", "wand", "wand_sparkles", @@ -1487,17 +1605,22 @@ LUCIDE_ICON_LIST = [ "washing_machine", "watch", "waves", + "waves_ladder", "waypoints", "webcam", - "webhook_off", "webhook", + "webhook_off", "weight", "wheat", "wheat_off", "whole_word", "wifi", + "wifi_high", + "wifi_low", "wifi_off", + "wifi_zero", "wind", + "wind_arrow_down", "wine", "wine_off", "workflow", diff --git a/reflex/components/lucide/icon.pyi b/reflex/components/lucide/icon.pyi index 7f59edec5..61697aa2a 100644 --- a/reflex/components/lucide/icon.pyi +++ b/reflex/components/lucide/icon.pyi @@ -154,6 +154,7 @@ LUCIDE_ICON_LIST = [ "ambulance", "ampersand", "ampersands", + "amphora", "anchor", "angry", "annoyed", @@ -241,6 +242,7 @@ LUCIDE_ICON_LIST = [ "baggage_claim", "ban", "banana", + "bandage", "banknote", "bar_chart", "bar_chart_2", @@ -278,8 +280,10 @@ LUCIDE_ICON_LIST = [ "between_horizontal_start", "between_vertical_end", "between_vertical_start", + "biceps_flexed", "bike", "binary", + "binoculars", "biohazard", "bird", "bitcoin", @@ -326,6 +330,7 @@ LUCIDE_ICON_LIST = [ "boom_box", "bot", "bot_message_square", + "bot_off", "box", "box_select", "boxes", @@ -337,6 +342,7 @@ LUCIDE_ICON_LIST = [ "brick_wall", "briefcase", "briefcase_business", + "briefcase_conveyor_belt", "briefcase_medical", "bring_to_front", "brush", @@ -353,9 +359,13 @@ LUCIDE_ICON_LIST = [ "cake_slice", "calculator", "calendar", + "calendar_1", + "calendar_arrow_down", + "calendar_arrow_up", "calendar_check", "calendar_check_2", "calendar_clock", + "calendar_cog", "calendar_days", "calendar_fold", "calendar_heart", @@ -366,6 +376,7 @@ LUCIDE_ICON_LIST = [ "calendar_plus_2", "calendar_range", "calendar_search", + "calendar_sync", "calendar_x", "calendar_x_2", "camera", @@ -390,6 +401,29 @@ LUCIDE_ICON_LIST = [ "castle", "cat", "cctv", + "chart_area", + "chart_bar", + "chart_bar_big", + "chart_bar_decreasing", + "chart_bar_increasing", + "chart_bar_stacked", + "chart_candlestick", + "chart_column", + "chart_column_big", + "chart_column_decreasing", + "chart_column_increasing", + "chart_column_stacked", + "chart_gantt", + "chart_line", + "chart_network", + "chart_no_axes_column", + "chart_no_axes_column_decreasing", + "chart_no_axes_column_increasing", + "chart_no_axes_combined", + "chart_no_axes_gantt", + "chart_pie", + "chart_scatter", + "chart_spline", "check", "check_check", "chef_hat", @@ -404,6 +438,7 @@ LUCIDE_ICON_LIST = [ "chevrons_down_up", "chevrons_left", "chevrons_left_right", + "chevrons_left_right_ellipsis", "chevrons_right", "chevrons_right_left", "chevrons_up", @@ -422,8 +457,8 @@ LUCIDE_ICON_LIST = [ "circle_arrow_out_up_right", "circle_arrow_right", "circle_arrow_up", - "circle_check_big", "circle_check", + "circle_check_big", "circle_chevron_down", "circle_chevron_left", "circle_chevron_right", @@ -435,13 +470,14 @@ LUCIDE_ICON_LIST = [ "circle_dot_dashed", "circle_ellipsis", "circle_equal", + "circle_fading_arrow_up", "circle_fading_plus", "circle_gauge", "circle_help", "circle_minus", "circle_off", - "circle_parking_off", "circle_parking", + "circle_parking_off", "circle_pause", "circle_percent", "circle_play", @@ -480,7 +516,11 @@ LUCIDE_ICON_LIST = [ "clock_7", "clock_8", "clock_9", + "clock_alert", + "clock_arrow_down", + "clock_arrow_up", "cloud", + "cloud_alert", "cloud_cog", "cloud_download", "cloud_drizzle", @@ -551,6 +591,7 @@ LUCIDE_ICON_LIST = [ "cup_soda", "currency", "cylinder", + "dam", "database", "database_backup", "database_zap", @@ -558,7 +599,9 @@ LUCIDE_ICON_LIST = [ "dessert", "diameter", "diamond", + "diamond_minus", "diamond_percent", + "diamond_plus", "dice_1", "dice_2", "dice_3", @@ -587,6 +630,7 @@ LUCIDE_ICON_LIST = [ "dribbble", "drill", "droplet", + "droplet_off", "droplets", "drum", "drumstick", @@ -602,12 +646,15 @@ LUCIDE_ICON_LIST = [ "ellipsis", "ellipsis_vertical", "equal", + "equal_approximately", "equal_not", "eraser", + "ethernet_port", "euro", "expand", "external_link", "eye", + "eye_closed", "eye_off", "facebook", "factory", @@ -627,6 +674,10 @@ LUCIDE_ICON_LIST = [ "file_bar_chart", "file_bar_chart_2", "file_box", + "file_chart_column", + "file_chart_column_increasing", + "file_chart_line", + "file_chart_pie", "file_check", "file_check_2", "file_clock", @@ -668,6 +719,7 @@ LUCIDE_ICON_LIST = [ "file_type", "file_type_2", "file_up", + "file_user", "file_video", "file_video_2", "file_volume", @@ -709,6 +761,7 @@ LUCIDE_ICON_LIST = [ "folder_check", "folder_clock", "folder_closed", + "folder_code", "folder_cog", "folder_dot", "folder_down", @@ -781,7 +834,12 @@ LUCIDE_ICON_LIST = [ "graduation_cap", "grape", "grid_2x2", + "grid_2x_2", + "grid_2x_2_check", + "grid_2x_2_plus", + "grid_2x_2_x", "grid_3x3", + "grid_3x_3", "grip", "grip_horizontal", "grip_vertical", @@ -810,6 +868,7 @@ LUCIDE_ICON_LIST = [ "heading_4", "heading_5", "heading_6", + "headphone_off", "headphones", "headset", "heart", @@ -827,14 +886,20 @@ LUCIDE_ICON_LIST = [ "hospital", "hotel", "hourglass", + "house", + "house_plug", + "house_plus", "ice_cream_bowl", "ice_cream_cone", + "id_card", "image", "image_down", "image_minus", "image_off", + "image_play", "image_plus", "image_up", + "image_upscale", "images", "import", "inbox", @@ -856,6 +921,7 @@ LUCIDE_ICON_LIST = [ "key_square", "keyboard", "keyboard_music", + "keyboard_off", "lamp", "lamp_ceiling", "lamp_desk", @@ -865,8 +931,9 @@ LUCIDE_ICON_LIST = [ "land_plot", "landmark", "languages", - "laptop_minimal", "laptop", + "laptop_minimal", + "laptop_minimal_check", "lasso", "lasso_select", "laugh", @@ -881,6 +948,8 @@ LUCIDE_ICON_LIST = [ "layout_template", "leaf", "leafy_green", + "lectern", + "letter_text", "library", "library_big", "life_buoy", @@ -893,10 +962,12 @@ LUCIDE_ICON_LIST = [ "link_2_off", "linkedin", "list", + "list_check", "list_checks", "list_collapse", "list_end", "list_filter", + "list_filter_plus", "list_minus", "list_music", "list_ordered", @@ -909,15 +980,17 @@ LUCIDE_ICON_LIST = [ "list_x", "loader", "loader_circle", + "loader_pinwheel", "locate", "locate_fixed", "locate_off", "lock", - "lock_keyhole_open", "lock_keyhole", + "lock_keyhole_open", "lock_open", "log_in", "log_out", + "logs", "lollipop", "luggage", "magnet", @@ -934,7 +1007,16 @@ LUCIDE_ICON_LIST = [ "mails", "map", "map_pin", + "map_pin_check", + "map_pin_check_inside", + "map_pin_house", + "map_pin_minus", + "map_pin_minus_inside", "map_pin_off", + "map_pin_plus", + "map_pin_plus_inside", + "map_pin_x", + "map_pin_x_inside", "map_pinned", "martini", "maximize", @@ -963,6 +1045,7 @@ LUCIDE_ICON_LIST = [ "message_square_diff", "message_square_dot", "message_square_heart", + "message_square_lock", "message_square_more", "message_square_off", "message_square_plus", @@ -974,8 +1057,9 @@ LUCIDE_ICON_LIST = [ "message_square_x", "messages_square", "mic", - "mic_vocal", "mic_off", + "mic_vocal", + "microchip", "microscope", "microwave", "milestone", @@ -986,6 +1070,7 @@ LUCIDE_ICON_LIST = [ "minus", "monitor", "monitor_check", + "monitor_cog", "monitor_dot", "monitor_down", "monitor_off", @@ -1001,8 +1086,10 @@ LUCIDE_ICON_LIST = [ "mountain", "mountain_snow", "mouse", + "mouse_off", "mouse_pointer", "mouse_pointer_2", + "mouse_pointer_ban", "mouse_pointer_click", "move", "move_3d", @@ -1039,10 +1126,13 @@ LUCIDE_ICON_LIST = [ "nut_off", "octagon", "octagon_alert", + "octagon_minus", "octagon_pause", "octagon_x", + "omega", "option", "orbit", + "origami", "package", "package_2", "package_check", @@ -1055,6 +1145,7 @@ LUCIDE_ICON_LIST = [ "paint_roller", "paintbrush", "paintbrush_2", + "paintbrush_vertical", "palette", "panel_bottom", "panel_bottom_close", @@ -1084,13 +1175,16 @@ LUCIDE_ICON_LIST = [ "pc_case", "pen", "pen_line", + "pen_off", "pen_tool", "pencil", "pencil_line", + "pencil_off", "pencil_ruler", "pentagon", "percent", "person_standing", + "philippine_peso", "phone", "phone_call", "phone_forwarded", @@ -1106,7 +1200,10 @@ LUCIDE_ICON_LIST = [ "pie_chart", "piggy_bank", "pilcrow", + "pilcrow_left", + "pilcrow_right", "pill", + "pill_bottle", "pin", "pin_off", "pipette", @@ -1132,6 +1229,7 @@ LUCIDE_ICON_LIST = [ "power_off", "presentation", "printer", + "printer_check", "projector", "proportions", "puzzle", @@ -1206,6 +1304,7 @@ LUCIDE_ICON_LIST = [ "satellite_dish", "save", "save_all", + "save_off", "scale", "scale_3d", "scaling", @@ -1213,7 +1312,9 @@ LUCIDE_ICON_LIST = [ "scan_barcode", "scan_eye", "scan_face", + "scan_heart", "scan_line", + "scan_qr_code", "scan_search", "scan_text", "scatter_chart", @@ -1229,6 +1330,7 @@ LUCIDE_ICON_LIST = [ "search_code", "search_slash", "search_x", + "section", "send", "send_horizontal", "send_to_back", @@ -1273,6 +1375,7 @@ LUCIDE_ICON_LIST = [ "signal_low", "signal_medium", "signal_zero", + "signature", "signpost", "signpost_big", "siren", @@ -1282,8 +1385,8 @@ LUCIDE_ICON_LIST = [ "slack", "slash", "slice", - "sliders_vertical", "sliders_horizontal", + "sliders_vertical", "smartphone", "smartphone_charging", "smartphone_nfc", @@ -1307,29 +1410,31 @@ LUCIDE_ICON_LIST = [ "sprout", "square", "square_activity", + "square_arrow_down", "square_arrow_down_left", "square_arrow_down_right", - "square_arrow_down", "square_arrow_left", "square_arrow_out_down_left", "square_arrow_out_down_right", "square_arrow_out_up_left", "square_arrow_out_up_right", "square_arrow_right", + "square_arrow_up", "square_arrow_up_left", "square_arrow_up_right", - "square_arrow_up", "square_asterisk", "square_bottom_dashed_scissors", - "square_check_big", + "square_chart_gantt", "square_check", + "square_check_big", "square_chevron_down", "square_chevron_left", "square_chevron_right", "square_chevron_up", "square_code", - "square_dashed_bottom_code", + "square_dashed", "square_dashed_bottom", + "square_dashed_bottom_code", "square_dashed_kanban", "square_dashed_mouse_pointer", "square_divide", @@ -1343,8 +1448,8 @@ LUCIDE_ICON_LIST = [ "square_menu", "square_minus", "square_mouse_pointer", - "square_parking_off", "square_parking", + "square_parking_off", "square_pen", "square_percent", "square_pi", @@ -1358,10 +1463,11 @@ LUCIDE_ICON_LIST = [ "square_slash", "square_split_horizontal", "square_split_vertical", + "square_square", "square_stack", "square_terminal", - "square_user_round", "square_user", + "square_user_round", "square_x", "squircle", "squirrel", @@ -1398,6 +1504,7 @@ LUCIDE_ICON_LIST = [ "table_cells_merge", "table_cells_split", "table_columns_split", + "table_of_contents", "table_properties", "table_rows_split", "tablet", @@ -1413,11 +1520,11 @@ LUCIDE_ICON_LIST = [ "tangent", "target", "telescope", + "tent", "tent_tree", "terminal", - "test_tube_diagonal", "test_tube", - "tent", + "test_tube_diagonal", "test_tubes", "text", "text_cursor", @@ -1438,11 +1545,14 @@ LUCIDE_ICON_LIST = [ "ticket_plus", "ticket_slash", "ticket_x", + "tickets", + "tickets_plane", "timer", "timer_off", "timer_reset", "toggle_left", "toggle_right", + "toilet", "tornado", "torus", "touchpad", @@ -1464,17 +1574,21 @@ LUCIDE_ICON_LIST = [ "trello", "trending_down", "trending_up", + "trending_up_down", "triangle", - "triangle_right", "triangle_alert", + "triangle_right", "trophy", "truck", "turtle", "tv", "tv_2", + "tv_minimal", + "tv_minimal_play", "twitch", "twitter", "type", + "type_outline", "umbrella", "umbrella_off", "underline", @@ -1485,8 +1599,8 @@ LUCIDE_ICON_LIST = [ "unfold_vertical", "ungroup", "university", - "unlink_2", "unlink", + "unlink_2", "unplug", "upload", "usb", @@ -1494,11 +1608,13 @@ LUCIDE_ICON_LIST = [ "user_check", "user_cog", "user_minus", + "user_pen", "user_plus", "user_round", "user_round_check", "user_round_cog", "user_round_minus", + "user_round_pen", "user_round_plus", "user_round_search", "user_round_x", @@ -1520,14 +1636,16 @@ LUCIDE_ICON_LIST = [ "videotape", "view", "voicemail", + "volleyball", "volume", "volume_1", "volume_2", + "volume_off", "volume_x", "vote", "wallet", - "wallet_minimal", "wallet_cards", + "wallet_minimal", "wallpaper", "wand", "wand_sparkles", @@ -1535,17 +1653,22 @@ LUCIDE_ICON_LIST = [ "washing_machine", "watch", "waves", + "waves_ladder", "waypoints", "webcam", - "webhook_off", "webhook", + "webhook_off", "weight", "wheat", "wheat_off", "whole_word", "wifi", + "wifi_high", + "wifi_low", "wifi_off", + "wifi_zero", "wind", + "wind_arrow_down", "wine", "wine_off", "workflow", From 879dcbd1bf86d128284825e6cfb64d3cd877e150 Mon Sep 17 00:00:00 2001 From: benedikt-bartscher <31854409+benedikt-bartscher@users.noreply.github.com> Date: Fri, 3 Jan 2025 22:00:48 +0100 Subject: [PATCH 07/44] improve dynamic route vars, no need to compute deps (#4551) --- reflex/state.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/reflex/state.py b/reflex/state.py index 1cd3e2c3e..c30a4038d 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -1193,6 +1193,8 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): continue dynamic_vars[param] = DynamicRouteVar( fget=func, + auto_deps=False, + deps=["router"], cache=True, _js_expr=param, _var_data=VarData.from_state(cls), From 4b89b8260b920459067d5ebfdfeb032bc03dcd67 Mon Sep 17 00:00:00 2001 From: Simon Young <40179067+Kastier1@users.noreply.github.com> Date: Fri, 3 Jan 2025 15:15:13 -0800 Subject: [PATCH 08/44] HOS-400: adding support for config (#4540) * HOS-400: adding support for config * ruff --------- Co-authored-by: simon --- reflex/reflex.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/reflex/reflex.py b/reflex/reflex.py index e333bfbd1..22fcb9fb8 100644 --- a/reflex/reflex.py +++ b/reflex/reflex.py @@ -485,6 +485,11 @@ def deploy( "--token", help="token to use for auth", ), + config_path: Optional[str] = typer.Option( + None, + "--config", + help="path to the config file", + ), ): """Deploy the app to the Reflex hosting service.""" from reflex_cli.utils import dependency @@ -540,6 +545,7 @@ def deploy( loglevel=type(loglevel).INFO, # type: ignore token=token, project=project, + config_path=config_path, ) From 41cb2d8cffa54ce396cddbe1525fbc55e143d344 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Fri, 3 Jan 2025 15:49:28 -0800 Subject: [PATCH 09/44] [ENG-4083] Track internal changes in dataclass instances (#4558) * [ENG-4083] Track internal changes in dataclass instances Create a dynamic subclass of MutableProxy with `__dataclass_fields__` set according to the dataclass being wrapped. * support dataclasses.asdict on MutableProxy instances --- reflex/state.py | 67 +++++++++++++++++++++++++++++++++++++-- tests/units/test_state.py | 45 +++++++++++++++++++------- 2 files changed, 98 insertions(+), 14 deletions(-) diff --git a/reflex/state.py b/reflex/state.py index c30a4038d..101155a97 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -3649,6 +3649,9 @@ def get_state_manager() -> StateManager: class MutableProxy(wrapt.ObjectProxy): """A proxy for a mutable object that tracks changes.""" + # Hint for finding the base class of the proxy. + __base_proxy__ = "MutableProxy" + # Methods on wrapped objects which should mark the state as dirty. __mark_dirty_attrs__ = { "add", @@ -3691,6 +3694,39 @@ class MutableProxy(wrapt.ObjectProxy): BaseModelV1, ) + # Dynamically generated classes for tracking dataclass mutations. + __dataclass_proxies__: Dict[type, type] = {} + + def __new__(cls, wrapped: Any, *args, **kwargs) -> MutableProxy: + """Create a proxy instance for a mutable object that tracks changes. + + Args: + wrapped: The object to proxy. + *args: Other args passed to MutableProxy (ignored). + **kwargs: Other kwargs passed to MutableProxy (ignored). + + Returns: + The proxy instance. + """ + if dataclasses.is_dataclass(wrapped): + wrapped_cls = type(wrapped) + wrapper_cls_name = wrapped_cls.__name__ + cls.__name__ + # Find the associated class + if wrapper_cls_name not in cls.__dataclass_proxies__: + # Create a new class that has the __dataclass_fields__ defined + cls.__dataclass_proxies__[wrapper_cls_name] = type( + wrapper_cls_name, + (cls,), + { + dataclasses._FIELDS: getattr( # pyright: ignore [reportGeneralTypeIssues] + wrapped_cls, + dataclasses._FIELDS, # pyright: ignore [reportGeneralTypeIssues] + ), + }, + ) + cls = cls.__dataclass_proxies__[wrapper_cls_name] + return super().__new__(cls) + def __init__(self, wrapped: Any, state: BaseState, field_name: str): """Create a proxy for a mutable object that tracks changes. @@ -3747,7 +3783,27 @@ class MutableProxy(wrapt.ObjectProxy): Returns: Whether the value is of a mutable type. """ - return isinstance(value, cls.__mutable_types__) + return isinstance(value, cls.__mutable_types__) or ( + dataclasses.is_dataclass(value) and not isinstance(value, Var) + ) + + @staticmethod + def _is_called_from_dataclasses_internal() -> bool: + """Check if the current function is called from dataclasses helper. + + Returns: + Whether the current function is called from dataclasses internal code. + """ + # Walk up the stack a bit to see if we are called from dataclasses + # internal code, for example `asdict` or `astuple`. + frame = inspect.currentframe() + for _ in range(5): + # Why not `inspect.stack()` -- this is much faster! + if not (frame := frame and frame.f_back): + break + if inspect.getfile(frame) == dataclasses.__file__: + return True + return False def _wrap_recursive(self, value: Any) -> Any: """Wrap a value recursively if it is mutable. @@ -3758,9 +3814,13 @@ class MutableProxy(wrapt.ObjectProxy): Returns: The wrapped value. """ + # When called from dataclasses internal code, return the unwrapped value + if self._is_called_from_dataclasses_internal(): + return value # Recursively wrap mutable types, but do not re-wrap MutableProxy instances. if self._is_mutable_type(value) and not isinstance(value, MutableProxy): - return type(self)( + base_cls = globals()[self.__base_proxy__] + return base_cls( wrapped=value, state=self._self_state, field_name=self._self_field_name, @@ -3968,6 +4028,9 @@ class ImmutableMutableProxy(MutableProxy): to modify the wrapped object when the StateProxy is immutable. """ + # Ensure that recursively wrapped proxies use ImmutableMutableProxy as base. + __base_proxy__ = "ImmutableMutableProxy" + def _mark_dirty( self, wrapped=None, diff --git a/tests/units/test_state.py b/tests/units/test_state.py index 39484752c..d6c48bd2b 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -1936,6 +1936,14 @@ def mock_app(mock_app_simple: rx.App, state_manager: StateManager) -> rx.App: return mock_app_simple +@dataclasses.dataclass +class ModelDC: + """A dataclass.""" + + foo: str = "bar" + ls: list[dict] = dataclasses.field(default_factory=list) + + @pytest.mark.asyncio async def test_state_proxy(grandchild_state: GrandchildState, mock_app: rx.App): """Test that the state proxy works. @@ -2038,6 +2046,7 @@ class BackgroundTaskState(BaseState): order: List[str] = [] dict_list: Dict[str, List[int]] = {"foo": [1, 2, 3]} + dc: ModelDC = ModelDC() def __init__(self, **kwargs): # noqa: D107 super().__init__(**kwargs) @@ -2063,10 +2072,18 @@ class BackgroundTaskState(BaseState): with pytest.raises(ImmutableStateError): self.order.append("bad idea") + with pytest.raises(ImmutableStateError): + # Cannot manipulate dataclass attributes. + self.dc.foo = "baz" + with pytest.raises(ImmutableStateError): # Even nested access to mutables raises an exception. self.dict_list["foo"].append(42) + with pytest.raises(ImmutableStateError): + # Cannot modify dataclass list attribute. + self.dc.ls.append({"foo": "bar"}) + with pytest.raises(ImmutableStateError): # Direct calling another handler that modifies state raises an exception. self.other() @@ -3582,13 +3599,6 @@ class ModelV2(BaseModelV2): foo: str = "bar" -@dataclasses.dataclass -class ModelDC: - """A dataclass.""" - - foo: str = "bar" - - class PydanticState(rx.State): """A state with pydantic BaseModel vars.""" @@ -3610,11 +3620,22 @@ def test_mutable_models(): assert state.dirty_vars == {"v2"} state.dirty_vars.clear() - # Not yet supported ENG-4083 - # assert isinstance(state.dc, MutableProxy) #noqa: ERA001 - # state.dc.foo = "baz" #noqa: ERA001 - # assert state.dirty_vars == {"dc"} #noqa: ERA001 - # state.dirty_vars.clear() #noqa: ERA001 + assert isinstance(state.dc, MutableProxy) + state.dc.foo = "baz" + assert state.dirty_vars == {"dc"} + state.dirty_vars.clear() + assert state.dirty_vars == set() + state.dc.ls.append({"hi": "reflex"}) + assert state.dirty_vars == {"dc"} + state.dirty_vars.clear() + assert state.dirty_vars == set() + assert dataclasses.asdict(state.dc) == {"foo": "baz", "ls": [{"hi": "reflex"}]} + assert dataclasses.astuple(state.dc) == ("baz", [{"hi": "reflex"}]) + # creating a new instance shouldn't mark the state dirty + assert dataclasses.replace(state.dc, foo="quuc") == ModelDC( + foo="quuc", ls=[{"hi": "reflex"}] + ) + assert state.dirty_vars == set() def test_get_value(): From 8477a1aba046921579ef59638a2034f81279c751 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Fri, 3 Jan 2025 15:49:46 -0800 Subject: [PATCH 10/44] BaseState.get_var_value helper to get a value from a Var (#4553) * BaseState.get_var_value helper to get a value from a Var When given a state Var or a LiteralVar, retrieve the actual value associated with the Var. For state Vars, the returned value is directly tied to the associated state and can be modified. Modifying LiteralVar values or ComputedVar values will have no useful effect. * Use Var[VAR_TYPE] annotation to take advantage of generics This requires rx.Field to pass typing where used. * Add case where get_var_value gets something that's not a var --- reflex/state.py | 40 ++++++++++++++++++++++++++++++++++++++ reflex/utils/exceptions.py | 4 ++++ tests/units/test_state.py | 36 +++++++++++++++++++++++++++++++--- 3 files changed, 77 insertions(+), 3 deletions(-) diff --git a/reflex/state.py b/reflex/state.py index 101155a97..a31aae032 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -107,6 +107,7 @@ from reflex.utils.exceptions import ( StateSchemaMismatchError, StateSerializationError, StateTooLargeError, + UnretrievableVarValueError, ) from reflex.utils.exec import is_testing_env from reflex.utils.serializers import serializer @@ -143,6 +144,9 @@ HANDLED_PICKLE_ERRORS = ( ValueError, ) +# For BaseState.get_var_value +VAR_TYPE = TypeVar("VAR_TYPE") + def _no_chain_background_task( state_cls: Type["BaseState"], name: str, fn: Callable @@ -1600,6 +1604,42 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): # Slow case - fetch missing parent states from redis. return await self._get_state_from_redis(state_cls) + async def get_var_value(self, var: Var[VAR_TYPE]) -> VAR_TYPE: + """Get the value of an rx.Var from another state. + + Args: + var: The var to get the value for. + + Returns: + The value of the var. + + Raises: + UnretrievableVarValueError: If the var does not have a literal value + or associated state. + """ + # Oopsie case: you didn't give me a Var... so get what you give. + if not isinstance(var, Var): + return var # type: ignore + + # Fast case: this is a literal var and the value is known. + if hasattr(var, "_var_value"): + return var._var_value + + var_data = var._get_all_var_data() + if var_data is None or not var_data.state: + raise UnretrievableVarValueError( + f"Unable to retrieve value for {var._js_expr}: not associated with any state." + ) + # Fastish case: this var belongs to this state + if var_data.state == self.get_full_name(): + return getattr(self, var_data.field_name) + + # Slow case: this var belongs to another state + other_state = await self.get_state( + self._get_root_state().get_class_substate(var_data.state) + ) + return getattr(other_state, var_data.field_name) + def _get_event_handler( self, event: Event ) -> tuple[BaseState | StateProxy, EventHandler]: diff --git a/reflex/utils/exceptions.py b/reflex/utils/exceptions.py index ae5ec0168..bceadc977 100644 --- a/reflex/utils/exceptions.py +++ b/reflex/utils/exceptions.py @@ -187,3 +187,7 @@ def raise_system_package_missing_error(package: str) -> NoReturn: class InvalidLockWarningThresholdError(ReflexError): """Raised when an invalid lock warning threshold is provided.""" + + +class UnretrievableVarValueError(ReflexError): + """Raised when the value of a var is not retrievable.""" diff --git a/tests/units/test_state.py b/tests/units/test_state.py index d6c48bd2b..41fac443e 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -60,6 +60,7 @@ from reflex.utils.exceptions import ( ReflexRuntimeError, SetUndefinedStateVarError, StateSerializationError, + UnretrievableVarValueError, ) from reflex.utils.format import json_dumps from reflex.vars.base import Var, computed_var @@ -115,7 +116,7 @@ class TestState(BaseState): # Set this class as not test one __test__ = False - num1: int + num1: rx.Field[int] num2: float = 3.14 key: str map_key: str = "a" @@ -163,7 +164,7 @@ class ChildState(TestState): """A child state fixture.""" value: str - count: int = 23 + count: rx.Field[int] = rx.field(23) def change_both(self, value: str, count: int): """Change both the value and count. @@ -1663,7 +1664,7 @@ async def state_manager(request) -> AsyncGenerator[StateManager, None]: @pytest.fixture() -def substate_token(state_manager, token): +def substate_token(state_manager, token) -> str: """A token + substate name for looking up in state manager. Args: @@ -3785,3 +3786,32 @@ async def test_upcast_event_handler_arg(handler, payload): state = UpcastState() async for update in state._process_event(handler, state, payload): assert update.delta == {UpcastState.get_full_name(): {"passed": True}} + + +@pytest.mark.asyncio +async def test_get_var_value(state_manager: StateManager, substate_token: str): + """Test that get_var_value works correctly. + + Args: + state_manager: The state manager to use. + substate_token: Token for the substate used by state_manager. + """ + state = await state_manager.get_state(substate_token) + + # State Var from same state + assert await state.get_var_value(TestState.num1) == 0 + state.num1 = 42 + assert await state.get_var_value(TestState.num1) == 42 + + # State Var from another state + child_state = await state.get_state(ChildState) + assert await state.get_var_value(ChildState.count) == 23 + child_state.count = 66 + assert await state.get_var_value(ChildState.count) == 66 + + # LiteralVar with known value + assert await state.get_var_value(rx.Var.create([1, 2, 3])) == [1, 2, 3] + + # Generic Var with no state + with pytest.raises(UnretrievableVarValueError): + await state.get_var_value(rx.Var("undefined")) From 316a0c9bde7d3d03ed56709df01e94c74c83c44f Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Fri, 3 Jan 2025 15:50:01 -0800 Subject: [PATCH 11/44] Do not allow call_function `callback` argument to be added afterwards (#4552) The default behavior for EventSpec is to treat the spec as a partial, wherein if additional unfilled arguments are available, and the event trigger wants to pass additional arguments, they will be applied positionally. However, it never makes sense to fill in `callback` with an event trigger arg. Therefore, if the callback is not provided initially, set it to None explicitly so that some invalid value cannot be added later. --- reflex/event.py | 2 +- tests/units/test_event.py | 18 ++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/reflex/event.py b/reflex/event.py index 0a4a01837..8b25c578b 100644 --- a/reflex/event.py +++ b/reflex/event.py @@ -1190,7 +1190,7 @@ def call_function( Returns: EventSpec: An event that will execute the client side javascript. """ - callback_kwargs = {} + callback_kwargs = {"callback": None} if callback is not None: callback_kwargs = { "callback": format.format_queue_events( diff --git a/tests/units/test_event.py b/tests/units/test_event.py index c5198a571..d7e993efa 100644 --- a/tests/units/test_event.py +++ b/tests/units/test_event.py @@ -223,12 +223,17 @@ def test_event_console_log(): ) assert ( format.format_event(spec) - == 'Event("_call_function", {function:(() => (console["log"]("message")))})' + == 'Event("_call_function", {function:(() => (console["log"]("message"))),callback:null})' ) spec = event.console_log(Var(_js_expr="message")) assert ( format.format_event(spec) - == 'Event("_call_function", {function:(() => (console["log"](message)))})' + == 'Event("_call_function", {function:(() => (console["log"](message))),callback:null})' + ) + spec2 = event.console_log(Var(_js_expr="message2")).add_args(Var("throwaway")) + assert ( + format.format_event(spec2) + == 'Event("_call_function", {function:(() => (console["log"](message2))),callback:null})' ) @@ -243,12 +248,17 @@ def test_event_window_alert(): ) assert ( format.format_event(spec) - == 'Event("_call_function", {function:(() => (window["alert"]("message")))})' + == 'Event("_call_function", {function:(() => (window["alert"]("message"))),callback:null})' ) spec = event.window_alert(Var(_js_expr="message")) assert ( format.format_event(spec) - == 'Event("_call_function", {function:(() => (window["alert"](message)))})' + == 'Event("_call_function", {function:(() => (window["alert"](message))),callback:null})' + ) + spec2 = event.window_alert(Var(_js_expr="message2")).add_args(Var("throwaway")) + assert ( + format.format_event(spec2) + == 'Event("_call_function", {function:(() => (window["alert"](message2))),callback:null})' ) From 438b31f27089c4fb069ec9ab0890bdb2af63bd7e Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Fri, 3 Jan 2025 15:50:38 -0800 Subject: [PATCH 12/44] [ENG-4005] Proxy backend requests on '/' to the frontend (#3300) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Proxy backend requests on '/' to the frontend If the optional extra `proxy` is installed, then the backend can handle all requests by proxy unrecognized routes to the frontend nextjs server. * Update lock file * pre-commit fu * AppHarness: set config frontend_port and backend_port * integration: frontend port and backend port should return the same content with proxying enabled by default in dev mode, both frontend and backend ports on / should return the same content. * Retry up to 100 times when proxying to frontend * Reduce retry attempts to 25 Fix log level passing to subprocess * scripts/wait_for_listening_port: primarily check HTTP responses if the port is up or not, we don't really care... the HTTP request needs to work and not return errors * aiohttp is an optional dep * adapt integration.sh for --backend-only (counter integration test) * woops * windows WTF? * scratching my head 🎄 * double WTF windows * Fix remaining integration tests --- .github/workflows/integration_tests.yml | 2 + .github/workflows/integration_tests_wsl.yml | 1 + poetry.lock | 575 +++++++++++++++++- pyproject.toml | 5 + reflex/app.py | 6 + reflex/config.py | 5 + reflex/proxy.py | 119 ++++ reflex/testing.py | 21 +- reflex/utils/console.py | 7 +- scripts/integration.sh | 2 +- scripts/wait_for_listening_port.py | 52 +- .../init-test/in_docker_test_script.sh | 2 +- 12 files changed, 782 insertions(+), 15 deletions(-) create mode 100644 reflex/proxy.py diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index b2304d463..6c79c27c9 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -79,6 +79,8 @@ jobs: run: | poetry run reflex export --backend-only - name: Check run --backend-only before init for counter example + env: + WAIT_FOR_LISTENING_PORT_ARGS: --path ping run: | poetry run bash scripts/integration.sh ./reflex-examples/counter dev 8001 --backend-only --backend-port 8001 - name: Init Website for counter example diff --git a/.github/workflows/integration_tests_wsl.yml b/.github/workflows/integration_tests_wsl.yml index 7a743252b..f6b3d395b 100644 --- a/.github/workflows/integration_tests_wsl.yml +++ b/.github/workflows/integration_tests_wsl.yml @@ -78,6 +78,7 @@ jobs: shell: wsl-bash {0} run: | export TELEMETRY_ENABLED=false + export WAIT_FOR_LISTENING_PORT_ARGS="--path ping" dos2unix scripts/integration.sh poetry run bash scripts/integration.sh ./reflex-examples/counter dev 8001 --backend-only --backend-port 8001 - name: Init Website for counter example diff --git a/poetry.lock b/poetry.lock index cc778d19b..ab65b25b4 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,5 +1,142 @@ # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. +[[package]] +name = "aiohappyeyeballs" +version = "2.4.3" +description = "Happy Eyeballs for asyncio" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiohappyeyeballs-2.4.3-py3-none-any.whl", hash = "sha256:8a7a83727b2756f394ab2895ea0765a0a8c475e3c71e98d43d76f22b4b435572"}, + {file = "aiohappyeyeballs-2.4.3.tar.gz", hash = "sha256:75cf88a15106a5002a8eb1dab212525c00d1f4c0fa96e551c9fbe6f09a621586"}, +] + +[[package]] +name = "aiohttp" +version = "3.10.10" +description = "Async http client/server framework (asyncio)" +optional = false +python-versions = ">=3.8" +files = [ + {file = "aiohttp-3.10.10-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:be7443669ae9c016b71f402e43208e13ddf00912f47f623ee5994e12fc7d4b3f"}, + {file = "aiohttp-3.10.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7b06b7843929e41a94ea09eb1ce3927865387e3e23ebe108e0d0d09b08d25be9"}, + {file = "aiohttp-3.10.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:333cf6cf8e65f6a1e06e9eb3e643a0c515bb850d470902274239fea02033e9a8"}, + {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:274cfa632350225ce3fdeb318c23b4a10ec25c0e2c880eff951a3842cf358ac1"}, + {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9e5e4a85bdb56d224f412d9c98ae4cbd032cc4f3161818f692cd81766eee65a"}, + {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b606353da03edcc71130b52388d25f9a30a126e04caef1fd637e31683033abd"}, + {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab5a5a0c7a7991d90446a198689c0535be89bbd6b410a1f9a66688f0880ec026"}, + {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:578a4b875af3e0daaf1ac6fa983d93e0bbfec3ead753b6d6f33d467100cdc67b"}, + {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8105fd8a890df77b76dd3054cddf01a879fc13e8af576805d667e0fa0224c35d"}, + {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3bcd391d083f636c06a68715e69467963d1f9600f85ef556ea82e9ef25f043f7"}, + {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fbc6264158392bad9df19537e872d476f7c57adf718944cc1e4495cbabf38e2a"}, + {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e48d5021a84d341bcaf95c8460b152cfbad770d28e5fe14a768988c461b821bc"}, + {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2609e9ab08474702cc67b7702dbb8a80e392c54613ebe80db7e8dbdb79837c68"}, + {file = "aiohttp-3.10.10-cp310-cp310-win32.whl", hash = "sha256:84afcdea18eda514c25bc68b9af2a2b1adea7c08899175a51fe7c4fb6d551257"}, + {file = "aiohttp-3.10.10-cp310-cp310-win_amd64.whl", hash = "sha256:9c72109213eb9d3874f7ac8c0c5fa90e072d678e117d9061c06e30c85b4cf0e6"}, + {file = "aiohttp-3.10.10-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c30a0eafc89d28e7f959281b58198a9fa5e99405f716c0289b7892ca345fe45f"}, + {file = "aiohttp-3.10.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:258c5dd01afc10015866114e210fb7365f0d02d9d059c3c3415382ab633fcbcb"}, + {file = "aiohttp-3.10.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:15ecd889a709b0080f02721255b3f80bb261c2293d3c748151274dfea93ac871"}, + {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3935f82f6f4a3820270842e90456ebad3af15810cf65932bd24da4463bc0a4c"}, + {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:413251f6fcf552a33c981c4709a6bba37b12710982fec8e558ae944bfb2abd38"}, + {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1720b4f14c78a3089562b8875b53e36b51c97c51adc53325a69b79b4b48ebcb"}, + {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:679abe5d3858b33c2cf74faec299fda60ea9de62916e8b67e625d65bf069a3b7"}, + {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79019094f87c9fb44f8d769e41dbb664d6e8fcfd62f665ccce36762deaa0e911"}, + {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe2fb38c2ed905a2582948e2de560675e9dfbee94c6d5ccdb1301c6d0a5bf092"}, + {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a3f00003de6eba42d6e94fabb4125600d6e484846dbf90ea8e48a800430cc142"}, + {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1bbb122c557a16fafc10354b9d99ebf2f2808a660d78202f10ba9d50786384b9"}, + {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:30ca7c3b94708a9d7ae76ff281b2f47d8eaf2579cd05971b5dc681db8caac6e1"}, + {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:df9270660711670e68803107d55c2b5949c2e0f2e4896da176e1ecfc068b974a"}, + {file = "aiohttp-3.10.10-cp311-cp311-win32.whl", hash = "sha256:aafc8ee9b742ce75044ae9a4d3e60e3d918d15a4c2e08a6c3c3e38fa59b92d94"}, + {file = "aiohttp-3.10.10-cp311-cp311-win_amd64.whl", hash = "sha256:362f641f9071e5f3ee6f8e7d37d5ed0d95aae656adf4ef578313ee585b585959"}, + {file = "aiohttp-3.10.10-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9294bbb581f92770e6ed5c19559e1e99255e4ca604a22c5c6397b2f9dd3ee42c"}, + {file = "aiohttp-3.10.10-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a8fa23fe62c436ccf23ff930149c047f060c7126eae3ccea005f0483f27b2e28"}, + {file = "aiohttp-3.10.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c6a5b8c7926ba5d8545c7dd22961a107526562da31a7a32fa2456baf040939f"}, + {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:007ec22fbc573e5eb2fb7dec4198ef8f6bf2fe4ce20020798b2eb5d0abda6138"}, + {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9627cc1a10c8c409b5822a92d57a77f383b554463d1884008e051c32ab1b3742"}, + {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:50edbcad60d8f0e3eccc68da67f37268b5144ecc34d59f27a02f9611c1d4eec7"}, + {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a45d85cf20b5e0d0aa5a8dca27cce8eddef3292bc29d72dcad1641f4ed50aa16"}, + {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b00807e2605f16e1e198f33a53ce3c4523114059b0c09c337209ae55e3823a8"}, + {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f2d4324a98062be0525d16f768a03e0bbb3b9fe301ceee99611dc9a7953124e6"}, + {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:438cd072f75bb6612f2aca29f8bd7cdf6e35e8f160bc312e49fbecab77c99e3a"}, + {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:baa42524a82f75303f714108fea528ccacf0386af429b69fff141ffef1c534f9"}, + {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a7d8d14fe962153fc681f6366bdec33d4356f98a3e3567782aac1b6e0e40109a"}, + {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c1277cd707c465cd09572a774559a3cc7c7a28802eb3a2a9472588f062097205"}, + {file = "aiohttp-3.10.10-cp312-cp312-win32.whl", hash = "sha256:59bb3c54aa420521dc4ce3cc2c3fe2ad82adf7b09403fa1f48ae45c0cbde6628"}, + {file = "aiohttp-3.10.10-cp312-cp312-win_amd64.whl", hash = "sha256:0e1b370d8007c4ae31ee6db7f9a2fe801a42b146cec80a86766e7ad5c4a259cf"}, + {file = "aiohttp-3.10.10-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ad7593bb24b2ab09e65e8a1d385606f0f47c65b5a2ae6c551db67d6653e78c28"}, + {file = "aiohttp-3.10.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1eb89d3d29adaf533588f209768a9c02e44e4baf832b08118749c5fad191781d"}, + {file = "aiohttp-3.10.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3fe407bf93533a6fa82dece0e74dbcaaf5d684e5a51862887f9eaebe6372cd79"}, + {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50aed5155f819873d23520919e16703fc8925e509abbb1a1491b0087d1cd969e"}, + {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f05e9727ce409358baa615dbeb9b969db94324a79b5a5cea45d39bdb01d82e6"}, + {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dffb610a30d643983aeb185ce134f97f290f8935f0abccdd32c77bed9388b42"}, + {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa6658732517ddabe22c9036479eabce6036655ba87a0224c612e1ae6af2087e"}, + {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:741a46d58677d8c733175d7e5aa618d277cd9d880301a380fd296975a9cdd7bc"}, + {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e00e3505cd80440f6c98c6d69269dcc2a119f86ad0a9fd70bccc59504bebd68a"}, + {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ffe595f10566f8276b76dc3a11ae4bb7eba1aac8ddd75811736a15b0d5311414"}, + {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdfcf6443637c148c4e1a20c48c566aa694fa5e288d34b20fcdc58507882fed3"}, + {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d183cf9c797a5291e8301790ed6d053480ed94070637bfaad914dd38b0981f67"}, + {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:77abf6665ae54000b98b3c742bc6ea1d1fb31c394bcabf8b5d2c1ac3ebfe7f3b"}, + {file = "aiohttp-3.10.10-cp313-cp313-win32.whl", hash = "sha256:4470c73c12cd9109db8277287d11f9dd98f77fc54155fc71a7738a83ffcc8ea8"}, + {file = "aiohttp-3.10.10-cp313-cp313-win_amd64.whl", hash = "sha256:486f7aabfa292719a2753c016cc3a8f8172965cabb3ea2e7f7436c7f5a22a151"}, + {file = "aiohttp-3.10.10-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:1b66ccafef7336a1e1f0e389901f60c1d920102315a56df85e49552308fc0486"}, + {file = "aiohttp-3.10.10-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:acd48d5b80ee80f9432a165c0ac8cbf9253eaddb6113269a5e18699b33958dbb"}, + {file = "aiohttp-3.10.10-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3455522392fb15ff549d92fbf4b73b559d5e43dc522588f7eb3e54c3f38beee7"}, + {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45c3b868724137f713a38376fef8120c166d1eadd50da1855c112fe97954aed8"}, + {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:da1dee8948d2137bb51fbb8a53cce6b1bcc86003c6b42565f008438b806cccd8"}, + {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c5ce2ce7c997e1971b7184ee37deb6ea9922ef5163c6ee5aa3c274b05f9e12fa"}, + {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28529e08fde6f12eba8677f5a8608500ed33c086f974de68cc65ab218713a59d"}, + {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f7db54c7914cc99d901d93a34704833568d86c20925b2762f9fa779f9cd2e70f"}, + {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:03a42ac7895406220124c88911ebee31ba8b2d24c98507f4a8bf826b2937c7f2"}, + {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:7e338c0523d024fad378b376a79faff37fafb3c001872a618cde1d322400a572"}, + {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:038f514fe39e235e9fef6717fbf944057bfa24f9b3db9ee551a7ecf584b5b480"}, + {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:64f6c17757251e2b8d885d728b6433d9d970573586a78b78ba8929b0f41d045a"}, + {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:93429602396f3383a797a2a70e5f1de5df8e35535d7806c9f91df06f297e109b"}, + {file = "aiohttp-3.10.10-cp38-cp38-win32.whl", hash = "sha256:c823bc3971c44ab93e611ab1a46b1eafeae474c0c844aff4b7474287b75fe49c"}, + {file = "aiohttp-3.10.10-cp38-cp38-win_amd64.whl", hash = "sha256:54ca74df1be3c7ca1cf7f4c971c79c2daf48d9aa65dea1a662ae18926f5bc8ce"}, + {file = "aiohttp-3.10.10-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:01948b1d570f83ee7bbf5a60ea2375a89dfb09fd419170e7f5af029510033d24"}, + {file = "aiohttp-3.10.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9fc1500fd2a952c5c8e3b29aaf7e3cc6e27e9cfc0a8819b3bce48cc1b849e4cc"}, + {file = "aiohttp-3.10.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f614ab0c76397661b90b6851a030004dac502e48260ea10f2441abd2207fbcc7"}, + {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00819de9e45d42584bed046314c40ea7e9aea95411b38971082cad449392b08c"}, + {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05646ebe6b94cc93407b3bf34b9eb26c20722384d068eb7339de802154d61bc5"}, + {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:998f3bd3cfc95e9424a6acd7840cbdd39e45bc09ef87533c006f94ac47296090"}, + {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9010c31cd6fa59438da4e58a7f19e4753f7f264300cd152e7f90d4602449762"}, + {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ea7ffc6d6d6f8a11e6f40091a1040995cdff02cfc9ba4c2f30a516cb2633554"}, + {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ef9c33cc5cbca35808f6c74be11eb7f5f6b14d2311be84a15b594bd3e58b5527"}, + {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ce0cdc074d540265bfeb31336e678b4e37316849d13b308607efa527e981f5c2"}, + {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:597a079284b7ee65ee102bc3a6ea226a37d2b96d0418cc9047490f231dc09fe8"}, + {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:7789050d9e5d0c309c706953e5e8876e38662d57d45f936902e176d19f1c58ab"}, + {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e7f8b04d83483577fd9200461b057c9f14ced334dcb053090cea1da9c8321a91"}, + {file = "aiohttp-3.10.10-cp39-cp39-win32.whl", hash = "sha256:c02a30b904282777d872266b87b20ed8cc0d1501855e27f831320f471d54d983"}, + {file = "aiohttp-3.10.10-cp39-cp39-win_amd64.whl", hash = "sha256:edfe3341033a6b53a5c522c802deb2079eee5cbfbb0af032a55064bd65c73a23"}, + {file = "aiohttp-3.10.10.tar.gz", hash = "sha256:0631dd7c9f0822cc61c88586ca76d5b5ada26538097d0f1df510b082bad3411a"}, +] + +[package.dependencies] +aiohappyeyeballs = ">=2.3.0" +aiosignal = ">=1.1.2" +async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} +attrs = ">=17.3.0" +frozenlist = ">=1.1.1" +multidict = ">=4.5,<7.0" +yarl = ">=1.12.0,<2.0" + +[package.extras] +speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] + +[[package]] +name = "aiosignal" +version = "1.3.1" +description = "aiosignal: a list of registered asynchronous callbacks" +optional = false +python-versions = ">=3.7" +files = [ + {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, + {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, +] + +[package.dependencies] +frozenlist = ">=1.1.0" + [[package]] name = "alembic" version = "1.14.0" @@ -52,15 +189,31 @@ doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] trio = ["trio (>=0.26.1)"] +[[package]] +name = "asgiproxy" +version = "0.1.1" +description = "Tools for building HTTP and Websocket proxies for the asynchronous ASGI protocol" +optional = false +python-versions = ">=3.6" +files = [ + {file = "asgiproxy-0.1.1-py3-none-any.whl", hash = "sha256:f5175d43691367c51cc972dda0631096e5f23b3536ca29d859be52de87844734"}, + {file = "asgiproxy-0.1.1.tar.gz", hash = "sha256:9dec4d1d8680277dd52b41813d1123383b8a475b8dc82314e5f6729c6c5fa177"}, +] + +[package.dependencies] +aiohttp = "*" +starlette = "*" +websockets = "*" + [[package]] name = "async-timeout" -version = "5.0.1" +version = "4.0.3" description = "Timeout context manager for asyncio programs" optional = false -python-versions = ">=3.8" +python-versions = ">=3.7" files = [ - {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, - {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, + {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, + {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, ] [[package]] @@ -619,6 +772,107 @@ docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2. testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] typing = ["typing-extensions (>=4.12.2)"] +[[package]] +name = "frozenlist" +version = "1.5.0" +description = "A list-like structure which implements collections.abc.MutableSequence" +optional = false +python-versions = ">=3.8" +files = [ + {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, + {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, + {file = "frozenlist-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba"}, + {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab"}, + {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5"}, + {file = "frozenlist-1.5.0-cp310-cp310-win32.whl", hash = "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb"}, + {file = "frozenlist-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4"}, + {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30"}, + {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5"}, + {file = "frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45"}, + {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2"}, + {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf"}, + {file = "frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942"}, + {file = "frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d"}, + {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21"}, + {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d"}, + {file = "frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6"}, + {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631"}, + {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f"}, + {file = "frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8"}, + {file = "frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f"}, + {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953"}, + {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0"}, + {file = "frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840"}, + {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9"}, + {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03"}, + {file = "frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c"}, + {file = "frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28"}, + {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dd94994fc91a6177bfaafd7d9fd951bc8689b0a98168aa26b5f543868548d3ca"}, + {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0da8bbec082bf6bf18345b180958775363588678f64998c2b7609e34719b10"}, + {file = "frozenlist-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73f2e31ea8dd7df61a359b731716018c2be196e5bb3b74ddba107f694fbd7604"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828afae9f17e6de596825cf4228ff28fbdf6065974e5ac1410cecc22f699d2b3"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1577515d35ed5649d52ab4319db757bb881ce3b2b796d7283e6634d99ace307"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2150cc6305a2c2ab33299453e2968611dacb970d2283a14955923062c8d00b10"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a72b7a6e3cd2725eff67cd64c8f13335ee18fc3c7befc05aed043d24c7b9ccb9"}, + {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c16d2fa63e0800723139137d667e1056bee1a1cf7965153d2d104b62855e9b99"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:17dcc32fc7bda7ce5875435003220a457bcfa34ab7924a49a1c19f55b6ee185c"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:97160e245ea33d8609cd2b8fd997c850b56db147a304a262abc2b3be021a9171"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f1e6540b7fa044eee0bb5111ada694cf3dc15f2b0347ca125ee9ca984d5e9e6e"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:91d6c171862df0a6c61479d9724f22efb6109111017c87567cfeb7b5d1449fdf"}, + {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c1fac3e2ace2eb1052e9f7c7db480818371134410e1f5c55d65e8f3ac6d1407e"}, + {file = "frozenlist-1.5.0-cp38-cp38-win32.whl", hash = "sha256:b97f7b575ab4a8af9b7bc1d2ef7f29d3afee2226bd03ca3875c16451ad5a7723"}, + {file = "frozenlist-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:374ca2dabdccad8e2a76d40b1d037f5bd16824933bf7bcea3e59c891fd4a0923"}, + {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9bbcdfaf4af7ce002694a4e10a0159d5a8d20056a12b05b45cea944a4953f972"}, + {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1893f948bf6681733aaccf36c5232c231e3b5166d607c5fa77773611df6dc336"}, + {file = "frozenlist-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2b5e23253bb709ef57a8e95e6ae48daa9ac5f265637529e4ce6b003a37b2621f"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f253985bb515ecd89629db13cb58d702035ecd8cfbca7d7a7e29a0e6d39af5f"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04a5c6babd5e8fb7d3c871dc8b321166b80e41b637c31a995ed844a6139942b6"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fe0f1c29ba24ba6ff6abf688cb0b7cf1efab6b6aa6adc55441773c252f7411"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226d72559fa19babe2ccd920273e767c96a49b9d3d38badd7c91a0fdeda8ea08"}, + {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b731db116ab3aedec558573c1a5eec78822b32292fe4f2f0345b7f697745c2"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:366d8f93e3edfe5a918c874702f78faac300209a4d5bf38352b2c1bdc07a766d"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1b96af8c582b94d381a1c1f51ffaedeb77c821c690ea5f01da3d70a487dd0a9b"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c03eff4a41bd4e38415cbed054bbaff4a075b093e2394b6915dca34a40d1e38b"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:50cf5e7ee9b98f22bdecbabf3800ae78ddcc26e4a435515fc72d97903e8488e0"}, + {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1e76bfbc72353269c44e0bc2cfe171900fbf7f722ad74c9a7b638052afe6a00c"}, + {file = "frozenlist-1.5.0-cp39-cp39-win32.whl", hash = "sha256:666534d15ba8f0fda3f53969117383d5dc021266b3c1a42c9ec4855e4b58b9d3"}, + {file = "frozenlist-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:5c28f4b5dbef8a0d8aad0d4de24d1e9e981728628afaf4ea0792f5d0939372f0"}, + {file = "frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3"}, + {file = "frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817"}, +] + [[package]] name = "greenlet" version = "3.1.1" @@ -1117,6 +1371,110 @@ files = [ {file = "more_itertools-10.5.0-py3-none-any.whl", hash = "sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef"}, ] +[[package]] +name = "multidict" +version = "6.1.0" +description = "multidict implementation" +optional = false +python-versions = ">=3.8" +files = [ + {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, + {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, + {file = "multidict-6.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429"}, + {file = "multidict-6.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160"}, + {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7"}, + {file = "multidict-6.1.0-cp310-cp310-win32.whl", hash = "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0"}, + {file = "multidict-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156"}, + {file = "multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351"}, + {file = "multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3"}, + {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753"}, + {file = "multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80"}, + {file = "multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436"}, + {file = "multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925"}, + {file = "multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6"}, + {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3"}, + {file = "multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133"}, + {file = "multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f"}, + {file = "multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44"}, + {file = "multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4"}, + {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6"}, + {file = "multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81"}, + {file = "multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a"}, + {file = "multidict-6.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d"}, + {file = "multidict-6.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492"}, + {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd"}, + {file = "multidict-6.1.0-cp38-cp38-win32.whl", hash = "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167"}, + {file = "multidict-6.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1"}, + {file = "multidict-6.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255"}, + {file = "multidict-6.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972"}, + {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43"}, + {file = "multidict-6.1.0-cp39-cp39-win32.whl", hash = "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada"}, + {file = "multidict-6.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a"}, + {file = "multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506"}, + {file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} + [[package]] name = "nh3" version = "0.2.19" @@ -1147,6 +1505,7 @@ files = [ {file = "nh3-0.2.19-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:00810cd5275f5c3f44b9eb0e521d1a841ee2f8023622de39ffc7d88bd533d8e0"}, {file = "nh3-0.2.19-cp38-abi3-win32.whl", hash = "sha256:7e98621856b0a911c21faa5eef8f8ea3e691526c2433f9afc2be713cb6fbdb48"}, {file = "nh3-0.2.19-cp38-abi3-win_amd64.whl", hash = "sha256:75c7cafb840f24430b009f7368945cb5ca88b2b54bb384ebfba495f16bc9c121"}, + {file = "nh3-0.2.19.tar.gz", hash = "sha256:790056b54c068ff8dceb443eaefb696b84beff58cca6c07afd754d17692a4804"}, ] [[package]] @@ -1608,6 +1967,113 @@ nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" +[[package]] +name = "propcache" +version = "0.2.0" +description = "Accelerated property cache" +optional = false +python-versions = ">=3.8" +files = [ + {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58"}, + {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b"}, + {file = "propcache-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:33ac8f098df0585c0b53009f039dfd913b38c1d2edafed0cedcc0c32a05aa110"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e48e8875e6c13909c800fa344cd54cc4b2b0db1d5f911f840458a500fde2c2"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388f3217649d6d59292b722d940d4d2e1e6a7003259eb835724092a1cca0203a"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f571aea50ba5623c308aa146eb650eebf7dbe0fd8c5d946e28343cb3b5aad577"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3dfafb44f7bb35c0c06eda6b2ab4bfd58f02729e7c4045e179f9a861b07c9850"}, + {file = "propcache-0.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3ebe9a75be7ab0b7da2464a77bb27febcb4fab46a34f9288f39d74833db7f61"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d2f0d0f976985f85dfb5f3d685697ef769faa6b71993b46b295cdbbd6be8cc37"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a3dc1a4b165283bd865e8f8cb5f0c64c05001e0718ed06250d8cac9bec115b48"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e0f07b42d2a50c7dd2d8675d50f7343d998c64008f1da5fef888396b7f84630"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e63e3e1e0271f374ed489ff5ee73d4b6e7c60710e1f76af5f0e1a6117cd26394"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:56bb5c98f058a41bb58eead194b4db8c05b088c93d94d5161728515bd52b052b"}, + {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7665f04d0c7f26ff8bb534e1c65068409bf4687aa2534faf7104d7182debb336"}, + {file = "propcache-0.2.0-cp310-cp310-win32.whl", hash = "sha256:7cf18abf9764746b9c8704774d8b06714bcb0a63641518a3a89c7f85cc02c2ad"}, + {file = "propcache-0.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:cfac69017ef97db2438efb854edf24f5a29fd09a536ff3a992b75990720cdc99"}, + {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:63f13bf09cc3336eb04a837490b8f332e0db41da66995c9fd1ba04552e516354"}, + {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608cce1da6f2672a56b24a015b42db4ac612ee709f3d29f27a00c943d9e851de"}, + {file = "propcache-0.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:466c219deee4536fbc83c08d09115249db301550625c7fef1c5563a584c9bc87"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6ed8db0a556343d566a5c124ee483ae113acc9a557a807d439bcecc44e7dfbb"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91997d9cb4a325b60d4e3f20967f8eb08dfcb32b22554d5ef78e6fd1dda743a2"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c7dde9e533c0a49d802b4f3f218fa9ad0a1ce21f2c2eb80d5216565202acab4"}, + {file = "propcache-0.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:97a58a28bcf63284e8b4d7b460cbee1edaab24634e82059c7b8c09e65284f178"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:945db8ee295d3af9dbdbb698cce9bbc5c59b5c3fe328bbc4387f59a8a35f998d"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39e104da444a34830751715f45ef9fc537475ba21b7f1f5b0f4d71a3b60d7fe2"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c5ecca8f9bab618340c8e848d340baf68bcd8ad90a8ecd7a4524a81c1764b3db"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c436130cc779806bdf5d5fae0d848713105472b8566b75ff70048c47d3961c5b"}, + {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:191db28dc6dcd29d1a3e063c3be0b40688ed76434622c53a284e5427565bbd9b"}, + {file = "propcache-0.2.0-cp311-cp311-win32.whl", hash = "sha256:5f2564ec89058ee7c7989a7b719115bdfe2a2fb8e7a4543b8d1c0cc4cf6478c1"}, + {file = "propcache-0.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e2e54267980349b723cff366d1e29b138b9a60fa376664a157a342689553f71"}, + {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ee7606193fb267be4b2e3b32714f2d58cad27217638db98a60f9efb5efeccc2"}, + {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:91ee8fc02ca52e24bcb77b234f22afc03288e1dafbb1f88fe24db308910c4ac7"}, + {file = "propcache-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e900bad2a8456d00a113cad8c13343f3b1f327534e3589acc2219729237a2e8"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f52a68c21363c45297aca15561812d542f8fc683c85201df0bebe209e349f793"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e41d67757ff4fbc8ef2af99b338bfb955010444b92929e9e55a6d4dcc3c4f09"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a64e32f8bd94c105cc27f42d3b658902b5bcc947ece3c8fe7bc1b05982f60e89"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55346705687dbd7ef0d77883ab4f6fabc48232f587925bdaf95219bae072491e"}, + {file = "propcache-0.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00181262b17e517df2cd85656fcd6b4e70946fe62cd625b9d74ac9977b64d8d9"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6994984550eaf25dd7fc7bd1b700ff45c894149341725bb4edc67f0ffa94efa4"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:56295eb1e5f3aecd516d91b00cfd8bf3a13991de5a479df9e27dd569ea23959c"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:439e76255daa0f8151d3cb325f6dd4a3e93043e6403e6491813bcaaaa8733887"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f6475a1b2ecb310c98c28d271a30df74f9dd436ee46d09236a6b750a7599ce57"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3444cdba6628accf384e349014084b1cacd866fbb88433cd9d279d90a54e0b23"}, + {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a9d9b4d0a9b38d1c391bb4ad24aa65f306c6f01b512e10a8a34a2dc5675d348"}, + {file = "propcache-0.2.0-cp312-cp312-win32.whl", hash = "sha256:69d3a98eebae99a420d4b28756c8ce6ea5a29291baf2dc9ff9414b42676f61d5"}, + {file = "propcache-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ad9c9b99b05f163109466638bd30ada1722abb01bbb85c739c50b6dc11f92dc3"}, + {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ecddc221a077a8132cf7c747d5352a15ed763b674c0448d811f408bf803d9ad7"}, + {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0e53cb83fdd61cbd67202735e6a6687a7b491c8742dfc39c9e01e80354956763"}, + {file = "propcache-0.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92fe151145a990c22cbccf9ae15cae8ae9eddabfc949a219c9f667877e40853d"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a21ef516d36909931a2967621eecb256018aeb11fc48656e3257e73e2e247a"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f88a4095e913f98988f5b338c1d4d5d07dbb0b6bad19892fd447484e483ba6b"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a5b3bb545ead161be780ee85a2b54fdf7092815995661947812dde94a40f6fb"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67aeb72e0f482709991aa91345a831d0b707d16b0257e8ef88a2ad246a7280bf"}, + {file = "propcache-0.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c997f8c44ec9b9b0bcbf2d422cc00a1d9b9c681f56efa6ca149a941e5560da2"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a66df3d4992bc1d725b9aa803e8c5a66c010c65c741ad901e260ece77f58d2f"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3ebbcf2a07621f29638799828b8d8668c421bfb94c6cb04269130d8de4fb7136"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1235c01ddaa80da8235741e80815ce381c5267f96cc49b1477fdcf8c047ef325"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3947483a381259c06921612550867b37d22e1df6d6d7e8361264b6d037595f44"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d5bed7f9805cc29c780f3aee05de3262ee7ce1f47083cfe9f77471e9d6777e83"}, + {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4a91d44379f45f5e540971d41e4626dacd7f01004826a18cb048e7da7e96544"}, + {file = "propcache-0.2.0-cp313-cp313-win32.whl", hash = "sha256:f902804113e032e2cdf8c71015651c97af6418363bea8d78dc0911d56c335032"}, + {file = "propcache-0.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8f188cfcc64fb1266f4684206c9de0e80f54622c3f22a910cbd200478aeae61e"}, + {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:53d1bd3f979ed529f0805dd35ddaca330f80a9a6d90bc0121d2ff398f8ed8861"}, + {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:83928404adf8fb3d26793665633ea79b7361efa0287dfbd372a7e74311d51ee6"}, + {file = "propcache-0.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:77a86c261679ea5f3896ec060be9dc8e365788248cc1e049632a1be682442063"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:218db2a3c297a3768c11a34812e63b3ac1c3234c3a086def9c0fee50d35add1f"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7735e82e3498c27bcb2d17cb65d62c14f1100b71723b68362872bca7d0913d90"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20a617c776f520c3875cf4511e0d1db847a076d720714ae35ffe0df3e440be68"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67b69535c870670c9f9b14a75d28baa32221d06f6b6fa6f77a0a13c5a7b0a5b9"}, + {file = "propcache-0.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4569158070180c3855e9c0791c56be3ceeb192defa2cdf6a3f39e54319e56b89"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:db47514ffdbd91ccdc7e6f8407aac4ee94cc871b15b577c1c324236b013ddd04"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:2a60ad3e2553a74168d275a0ef35e8c0a965448ffbc3b300ab3a5bb9956c2162"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:662dd62358bdeaca0aee5761de8727cfd6861432e3bb828dc2a693aa0471a563"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:25a1f88b471b3bc911d18b935ecb7115dff3a192b6fef46f0bfaf71ff4f12418"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:f60f0ac7005b9f5a6091009b09a419ace1610e163fa5deaba5ce3484341840e7"}, + {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:74acd6e291f885678631b7ebc85d2d4aec458dd849b8c841b57ef04047833bed"}, + {file = "propcache-0.2.0-cp38-cp38-win32.whl", hash = "sha256:d9b6ddac6408194e934002a69bcaadbc88c10b5f38fb9307779d1c629181815d"}, + {file = "propcache-0.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:676135dcf3262c9c5081cc8f19ad55c8a64e3f7282a21266d05544450bffc3a5"}, + {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:25c8d773a62ce0451b020c7b29a35cfbc05de8b291163a7a0f3b7904f27253e6"}, + {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:375a12d7556d462dc64d70475a9ee5982465fbb3d2b364f16b86ba9135793638"}, + {file = "propcache-0.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1ec43d76b9677637a89d6ab86e1fef70d739217fefa208c65352ecf0282be957"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45eec587dafd4b2d41ac189c2156461ebd0c1082d2fe7013571598abb8505d1"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc092ba439d91df90aea38168e11f75c655880c12782facf5cf9c00f3d42b562"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa1076244f54bb76e65e22cb6910365779d5c3d71d1f18b275f1dfc7b0d71b4d"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:682a7c79a2fbf40f5dbb1eb6bfe2cd865376deeac65acf9beb607505dced9e12"}, + {file = "propcache-0.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e40876731f99b6f3c897b66b803c9e1c07a989b366c6b5b475fafd1f7ba3fb8"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:363ea8cd3c5cb6679f1c2f5f1f9669587361c062e4899fce56758efa928728f8"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:140fbf08ab3588b3468932974a9331aff43c0ab8a2ec2c608b6d7d1756dbb6cb"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e70fac33e8b4ac63dfc4c956fd7d85a0b1139adcfc0d964ce288b7c527537fea"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b33d7a286c0dc1a15f5fc864cc48ae92a846df287ceac2dd499926c3801054a6"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f6d5749fdd33d90e34c2efb174c7e236829147a2713334d708746e94c4bde40d"}, + {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22aa8f2272d81d9317ff5756bb108021a056805ce63dd3630e27d042c8092798"}, + {file = "propcache-0.2.0-cp39-cp39-win32.whl", hash = "sha256:73e4b40ea0eda421b115248d7e79b59214411109a5bc47d0d48e4c73e3b8fcf9"}, + {file = "propcache-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:9517d5e9e0731957468c29dbfd0f976736a0e55afaea843726e887f36fe017df"}, + {file = "propcache-0.2.0-py3-none-any.whl", hash = "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036"}, + {file = "propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70"}, +] + [[package]] name = "psutil" version = "6.1.0" @@ -3054,6 +3520,102 @@ files = [ [package.dependencies] h11 = ">=0.9.0,<1" +[[package]] +name = "yarl" +version = "1.17.1" +description = "Yet another URL library" +optional = false +python-versions = ">=3.9" +files = [ + {file = "yarl-1.17.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1794853124e2f663f0ea54efb0340b457f08d40a1cef78edfa086576179c91"}, + {file = "yarl-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fbea1751729afe607d84acfd01efd95e3b31db148a181a441984ce9b3d3469da"}, + {file = "yarl-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ee427208c675f1b6e344a1f89376a9613fc30b52646a04ac0c1f6587c7e46ec"}, + {file = "yarl-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b74ff4767d3ef47ffe0cd1d89379dc4d828d4873e5528976ced3b44fe5b0a21"}, + {file = "yarl-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:62a91aefff3d11bf60e5956d340eb507a983a7ec802b19072bb989ce120cd948"}, + {file = "yarl-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:846dd2e1243407133d3195d2d7e4ceefcaa5f5bf7278f0a9bda00967e6326b04"}, + {file = "yarl-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e844be8d536afa129366d9af76ed7cb8dfefec99f5f1c9e4f8ae542279a6dc3"}, + {file = "yarl-1.17.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc7c92c1baa629cb03ecb0c3d12564f172218fb1739f54bf5f3881844daadc6d"}, + {file = "yarl-1.17.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ae3476e934b9d714aa8000d2e4c01eb2590eee10b9d8cd03e7983ad65dfbfcba"}, + {file = "yarl-1.17.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c7e177c619342e407415d4f35dec63d2d134d951e24b5166afcdfd1362828e17"}, + {file = "yarl-1.17.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64cc6e97f14cf8a275d79c5002281f3040c12e2e4220623b5759ea7f9868d6a5"}, + {file = "yarl-1.17.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:84c063af19ef5130084db70ada40ce63a84f6c1ef4d3dbc34e5e8c4febb20822"}, + {file = "yarl-1.17.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:482c122b72e3c5ec98f11457aeb436ae4aecca75de19b3d1de7cf88bc40db82f"}, + {file = "yarl-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:380e6c38ef692b8fd5a0f6d1fa8774d81ebc08cfbd624b1bca62a4d4af2f9931"}, + {file = "yarl-1.17.1-cp310-cp310-win32.whl", hash = "sha256:16bca6678a83657dd48df84b51bd56a6c6bd401853aef6d09dc2506a78484c7b"}, + {file = "yarl-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:561c87fea99545ef7d692403c110b2f99dced6dff93056d6e04384ad3bc46243"}, + {file = "yarl-1.17.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cbad927ea8ed814622305d842c93412cb47bd39a496ed0f96bfd42b922b4a217"}, + {file = "yarl-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fca4b4307ebe9c3ec77a084da3a9d1999d164693d16492ca2b64594340999988"}, + {file = "yarl-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff5c6771c7e3511a06555afa317879b7db8d640137ba55d6ab0d0c50425cab75"}, + {file = "yarl-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b29beab10211a746f9846baa39275e80034e065460d99eb51e45c9a9495bcca"}, + {file = "yarl-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a52a1ffdd824fb1835272e125385c32fd8b17fbdefeedcb4d543cc23b332d74"}, + {file = "yarl-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:58c8e9620eb82a189c6c40cb6b59b4e35b2ee68b1f2afa6597732a2b467d7e8f"}, + {file = "yarl-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d216e5d9b8749563c7f2c6f7a0831057ec844c68b4c11cb10fc62d4fd373c26d"}, + {file = "yarl-1.17.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:881764d610e3269964fc4bb3c19bb6fce55422828e152b885609ec176b41cf11"}, + {file = "yarl-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8c79e9d7e3d8a32d4824250a9c6401194fb4c2ad9a0cec8f6a96e09a582c2cc0"}, + {file = "yarl-1.17.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:299f11b44d8d3a588234adbe01112126010bd96d9139c3ba7b3badd9829261c3"}, + {file = "yarl-1.17.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:cc7d768260f4ba4ea01741c1b5fe3d3a6c70eb91c87f4c8761bbcce5181beafe"}, + {file = "yarl-1.17.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:de599af166970d6a61accde358ec9ded821234cbbc8c6413acfec06056b8e860"}, + {file = "yarl-1.17.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2b24ec55fad43e476905eceaf14f41f6478780b870eda5d08b4d6de9a60b65b4"}, + {file = "yarl-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9fb815155aac6bfa8d86184079652c9715c812d506b22cfa369196ef4e99d1b4"}, + {file = "yarl-1.17.1-cp311-cp311-win32.whl", hash = "sha256:7615058aabad54416ddac99ade09a5510cf77039a3b903e94e8922f25ed203d7"}, + {file = "yarl-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:14bc88baa44e1f84164a392827b5defb4fa8e56b93fecac3d15315e7c8e5d8b3"}, + {file = "yarl-1.17.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:327828786da2006085a4d1feb2594de6f6d26f8af48b81eb1ae950c788d97f61"}, + {file = "yarl-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cc353841428d56b683a123a813e6a686e07026d6b1c5757970a877195f880c2d"}, + {file = "yarl-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c73df5b6e8fabe2ddb74876fb82d9dd44cbace0ca12e8861ce9155ad3c886139"}, + {file = "yarl-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bdff5e0995522706c53078f531fb586f56de9c4c81c243865dd5c66c132c3b5"}, + {file = "yarl-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:06157fb3c58f2736a5e47c8fcbe1afc8b5de6fb28b14d25574af9e62150fcaac"}, + {file = "yarl-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1654ec814b18be1af2c857aa9000de7a601400bd4c9ca24629b18486c2e35463"}, + {file = "yarl-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f6595c852ca544aaeeb32d357e62c9c780eac69dcd34e40cae7b55bc4fb1147"}, + {file = "yarl-1.17.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:459e81c2fb920b5f5df744262d1498ec2c8081acdcfe18181da44c50f51312f7"}, + {file = "yarl-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7e48cdb8226644e2fbd0bdb0a0f87906a3db07087f4de77a1b1b1ccfd9e93685"}, + {file = "yarl-1.17.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d9b6b28a57feb51605d6ae5e61a9044a31742db557a3b851a74c13bc61de5172"}, + {file = "yarl-1.17.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e594b22688d5747b06e957f1ef822060cb5cb35b493066e33ceac0cf882188b7"}, + {file = "yarl-1.17.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5f236cb5999ccd23a0ab1bd219cfe0ee3e1c1b65aaf6dd3320e972f7ec3a39da"}, + {file = "yarl-1.17.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a2a64e62c7a0edd07c1c917b0586655f3362d2c2d37d474db1a509efb96fea1c"}, + {file = "yarl-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d0eea830b591dbc68e030c86a9569826145df485b2b4554874b07fea1275a199"}, + {file = "yarl-1.17.1-cp312-cp312-win32.whl", hash = "sha256:46ddf6e0b975cd680eb83318aa1d321cb2bf8d288d50f1754526230fcf59ba96"}, + {file = "yarl-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:117ed8b3732528a1e41af3aa6d4e08483c2f0f2e3d3d7dca7cf538b3516d93df"}, + {file = "yarl-1.17.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5d1d42556b063d579cae59e37a38c61f4402b47d70c29f0ef15cee1acaa64488"}, + {file = "yarl-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0167540094838ee9093ef6cc2c69d0074bbf84a432b4995835e8e5a0d984374"}, + {file = "yarl-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2f0a6423295a0d282d00e8701fe763eeefba8037e984ad5de44aa349002562ac"}, + {file = "yarl-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5b078134f48552c4d9527db2f7da0b5359abd49393cdf9794017baec7506170"}, + {file = "yarl-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d401f07261dc5aa36c2e4efc308548f6ae943bfff20fcadb0a07517a26b196d8"}, + {file = "yarl-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5f1ac7359e17efe0b6e5fec21de34145caef22b260e978336f325d5c84e6938"}, + {file = "yarl-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f63d176a81555984e91f2c84c2a574a61cab7111cc907e176f0f01538e9ff6e"}, + {file = "yarl-1.17.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e275792097c9f7e80741c36de3b61917aebecc08a67ae62899b074566ff8556"}, + {file = "yarl-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:81713b70bea5c1386dc2f32a8f0dab4148a2928c7495c808c541ee0aae614d67"}, + {file = "yarl-1.17.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:aa46dce75078fceaf7cecac5817422febb4355fbdda440db55206e3bd288cfb8"}, + {file = "yarl-1.17.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1ce36ded585f45b1e9bb36d0ae94765c6608b43bd2e7f5f88079f7a85c61a4d3"}, + {file = "yarl-1.17.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2d374d70fdc36f5863b84e54775452f68639bc862918602d028f89310a034ab0"}, + {file = "yarl-1.17.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2d9f0606baaec5dd54cb99667fcf85183a7477f3766fbddbe3f385e7fc253299"}, + {file = "yarl-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b0341e6d9a0c0e3cdc65857ef518bb05b410dbd70d749a0d33ac0f39e81a4258"}, + {file = "yarl-1.17.1-cp313-cp313-win32.whl", hash = "sha256:2e7ba4c9377e48fb7b20dedbd473cbcbc13e72e1826917c185157a137dac9df2"}, + {file = "yarl-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:949681f68e0e3c25377462be4b658500e85ca24323d9619fdc41f68d46a1ffda"}, + {file = "yarl-1.17.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8994b29c462de9a8fce2d591028b986dbbe1b32f3ad600b2d3e1c482c93abad6"}, + {file = "yarl-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f9cbfbc5faca235fbdf531b93aa0f9f005ec7d267d9d738761a4d42b744ea159"}, + {file = "yarl-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b40d1bf6e6f74f7c0a567a9e5e778bbd4699d1d3d2c0fe46f4b717eef9e96b95"}, + {file = "yarl-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5efe0661b9fcd6246f27957f6ae1c0eb29bc60552820f01e970b4996e016004"}, + {file = "yarl-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5c4804e4039f487e942c13381e6c27b4b4e66066d94ef1fae3f6ba8b953f383"}, + {file = "yarl-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5d6a6c9602fd4598fa07e0389e19fe199ae96449008d8304bf5d47cb745462e"}, + {file = "yarl-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4c9156c4d1eb490fe374fb294deeb7bc7eaccda50e23775b2354b6a6739934"}, + {file = "yarl-1.17.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6324274b4e0e2fa1b3eccb25997b1c9ed134ff61d296448ab8269f5ac068c4c"}, + {file = "yarl-1.17.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d8a8b74d843c2638f3864a17d97a4acda58e40d3e44b6303b8cc3d3c44ae2d29"}, + {file = "yarl-1.17.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:7fac95714b09da9278a0b52e492466f773cfe37651cf467a83a1b659be24bf71"}, + {file = "yarl-1.17.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c180ac742a083e109c1a18151f4dd8675f32679985a1c750d2ff806796165b55"}, + {file = "yarl-1.17.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:578d00c9b7fccfa1745a44f4eddfdc99d723d157dad26764538fbdda37209857"}, + {file = "yarl-1.17.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:1a3b91c44efa29e6c8ef8a9a2b583347998e2ba52c5d8280dbd5919c02dfc3b5"}, + {file = "yarl-1.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a7ac5b4984c468ce4f4a553df281450df0a34aefae02e58d77a0847be8d1e11f"}, + {file = "yarl-1.17.1-cp39-cp39-win32.whl", hash = "sha256:7294e38f9aa2e9f05f765b28ffdc5d81378508ce6dadbe93f6d464a8c9594473"}, + {file = "yarl-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:eb6dce402734575e1a8cc0bb1509afca508a400a57ce13d306ea2c663bad1138"}, + {file = "yarl-1.17.1-py3-none-any.whl", hash = "sha256:f1790a4b1e8e8e028c391175433b9c8122c39b46e1663228158e61e6f915bf06"}, + {file = "yarl-1.17.1.tar.gz", hash = "sha256:067a63fcfda82da6b198fa73079b1ca40b7c9b7994995b6ee38acda728b64d47"}, +] + +[package.dependencies] +idna = ">=2.0" +multidict = ">=4.0" +propcache = ">=0.2.0" + [[package]] name = "zipp" version = "3.21.0" @@ -3073,7 +3635,10 @@ enabler = ["pytest-enabler (>=2.2)"] test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] +[extras] +proxy = ["asgiproxy"] + [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "d62cd1897d8f73e9aad9e907beb82be509dc5e33d8f37b36ebf26ad1f3075a9f" +content-hash = "a2923e478d2f16aa84c5c36b4b9169e7a2d263e3b1060585bf33b9980a10324a" diff --git a/pyproject.toml b/pyproject.toml index 21bbec91f..e50ef88bb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -50,6 +50,7 @@ setuptools = ">=75.0" httpx = ">=0.25.1,<1.0" twine = ">=4.0.0,<7.0" tomlkit = ">=0.12.4,<1.0" +asgiproxy = { version = "==0.1.1", optional = true } lazy_loader = ">=0.4" reflex-chakra = ">=0.6.0" typing_extensions = ">=4.6.0" @@ -73,10 +74,14 @@ selenium = ">=4.11.0,<5.0" pytest-benchmark = ">=4.0.0,<6.0" playwright = ">=1.46.0" pytest-playwright = ">=0.5.1" +asgiproxy = "==0.1.1" [tool.poetry.scripts] reflex = "reflex.reflex:cli" +[tool.poetry.extras] +proxy = ["asgiproxy"] + [build-system] requires = ["poetry-core>=1.5.1"] build-backend = "poetry.core.masonry.api" diff --git a/reflex/app.py b/reflex/app.py index 032b198f6..259fcca29 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -331,6 +331,12 @@ class App(MiddlewareMixin, LifespanMixin): self.register_lifespan_task(windows_hot_reload_lifespan_hack) + # Enable proxying to frontend server. + if not environment.REFLEX_BACKEND_ONLY.get(): + from reflex.proxy import proxy_middleware + + self.register_lifespan_task(proxy_middleware) + def _enable_state(self) -> None: """Enable state for the app.""" if not self.state: diff --git a/reflex/config.py b/reflex/config.py index 0579b019f..87d9ce665 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -26,6 +26,7 @@ from typing import ( from typing_extensions import Annotated, get_type_hints +from reflex.utils.console import set_log_level from reflex.utils.exceptions import ConfigError, EnvironmentVarValueError from reflex.utils.types import GenericType, is_union, value_inside_optional @@ -599,6 +600,7 @@ class Config(Base): class Config: """Pydantic config for the config.""" + use_enum_values = False validate_assignment = True # The name of the app (should match the name of the app directory). @@ -718,6 +720,9 @@ class Config(Base): self._non_default_attributes.update(kwargs) self._replace_defaults(**kwargs) + # Set the log level for this process + set_log_level(self.loglevel) + if ( self.state_manager_mode == constants.StateManagerMode.REDIS and not self.redis_url diff --git a/reflex/proxy.py b/reflex/proxy.py new file mode 100644 index 000000000..47f36c8bd --- /dev/null +++ b/reflex/proxy.py @@ -0,0 +1,119 @@ +"""Handle proxying frontend requests from the backend server.""" + +from __future__ import annotations + +import asyncio +from contextlib import asynccontextmanager +from typing import Any, AsyncGenerator +from urllib.parse import urlparse + +from fastapi import FastAPI +from starlette.types import ASGIApp, Receive, Scope, Send + +from .config import get_config +from .utils import console + +try: + import aiohttp + from asgiproxy.config import BaseURLProxyConfigMixin, ProxyConfig + from asgiproxy.context import ProxyContext + from asgiproxy.proxies.http import proxy_http + from asgiproxy.simple_proxy import make_simple_proxy_app +except ImportError: + + @asynccontextmanager + async def proxy_middleware(*args, **kwargs) -> AsyncGenerator[None, None]: + """A no-op proxy middleware for when asgiproxy is not installed. + + Args: + *args: The positional arguments. + **kwargs: The keyword arguments. + + Yields: + None + """ + yield +else: + MAX_PROXY_RETRY = 25 + + async def proxy_http_with_retry( + *, + context: ProxyContext, + scope: Scope, + receive: Receive, + send: Send, + ) -> Any: + """Proxy an HTTP request with retries. + + Args: + context: The proxy context. + scope: The request scope. + receive: The receive channel. + send: The send channel. + + Returns: + The response from `proxy_http`. + """ + for _attempt in range(MAX_PROXY_RETRY): + try: + return await proxy_http( + context=context, + scope=scope, + receive=receive, + send=send, + ) + except aiohttp.ClientError as err: # noqa: PERF203 + console.debug( + f"Retrying request {scope['path']} due to client error {err!r}." + ) + await asyncio.sleep(0.3) + except Exception as ex: + console.debug( + f"Retrying request {scope['path']} due to unhandled exception {ex!r}." + ) + await asyncio.sleep(0.3) + + def _get_proxy_app_with_context(frontend_host: str) -> tuple[ProxyContext, ASGIApp]: + """Get the proxy app with the given frontend host. + + Args: + frontend_host: The frontend host to proxy requests to. + + Returns: + The proxy context and app. + """ + + class LocalProxyConfig(BaseURLProxyConfigMixin, ProxyConfig): + upstream_base_url = frontend_host + rewrite_host_header = urlparse(upstream_base_url).netloc + + proxy_context = ProxyContext(LocalProxyConfig()) + proxy_app = make_simple_proxy_app( + proxy_context, proxy_http_handler=proxy_http_with_retry + ) + return proxy_context, proxy_app + + @asynccontextmanager + async def proxy_middleware( # pyright: ignore[reportGeneralTypeIssues] + app: FastAPI, + ) -> AsyncGenerator[None, None]: + """A middleware to proxy requests to the separate frontend server. + + The proxy is installed on the / endpoint of the FastAPI instance. + + Args: + app: The FastAPI instance. + + Yields: + None + """ + config = get_config() + backend_port = config.backend_port + frontend_host = f"http://localhost:{config.frontend_port}" + proxy_context, proxy_app = _get_proxy_app_with_context(frontend_host) + app.mount("/", proxy_app) + console.debug( + f"Proxying '/' requests on port {backend_port} to {frontend_host}" + ) + async with proxy_context: + yield diff --git a/reflex/testing.py b/reflex/testing.py index b3dedf398..582d7f8c9 100644 --- a/reflex/testing.py +++ b/reflex/testing.py @@ -44,6 +44,7 @@ import reflex.utils.format import reflex.utils.prerequisites import reflex.utils.processes from reflex.config import environment +from reflex.proxy import proxy_middleware from reflex.state import ( BaseState, StateManager, @@ -298,6 +299,9 @@ class AppHarness: self.state_manager = StateManagerRedis.create(self.app_instance.state) else: self.state_manager = self.app_instance._state_manager + # Disable proxy for app harness tests. + if proxy_middleware in self.app_instance.lifespan_tasks: + self.app_instance.lifespan_tasks.remove(proxy_middleware) def _reload_state_module(self): """Reload the rx.State module to avoid conflict when reloading.""" @@ -365,9 +369,12 @@ class AppHarness: def _start_frontend(self): # Set up the frontend. with chdir(self.app_path): + backend_host, backend_port = self._poll_for_servers().getsockname() config = reflex.config.get_config() + config.backend_port = backend_port config.api_url = "http://{0}:{1}".format( - *self._poll_for_servers().getsockname(), + backend_host, + backend_port, ) reflex.utils.build.setup_frontend(self.app_path) @@ -392,6 +399,7 @@ class AppHarness: self.frontend_url = m.group(1) config = reflex.config.get_config() config.deploy_url = self.frontend_url + config.frontend_port = int(self.frontend_url.rpartition(":")[2]) break if self.frontend_url is None: raise RuntimeError("Frontend did not start") @@ -915,17 +923,20 @@ class AppHarnessProd(AppHarness): root=web_root, error_page_map=error_page_map, ) as self.frontend_server: - self.frontend_url = "http://localhost:{1}".format( - *self.frontend_server.socket.getsockname() - ) + config = reflex.config.get_config() + config.frontend_port = self.frontend_server.server_address[1] + self.frontend_url = f"http://localhost:{config.frontend_port}" self.frontend_server.serve_forever() def _start_frontend(self): # Set up the frontend. with chdir(self.app_path): + backend_host, backend_port = self._poll_for_servers().getsockname() config = reflex.config.get_config() + config.backend_port = backend_port config.api_url = "http://{0}:{1}".format( - *self._poll_for_servers().getsockname(), + backend_host, + backend_port, ) reflex.reflex.export( zipping=False, diff --git a/reflex/utils/console.py b/reflex/utils/console.py index be545140a..1c08a04b6 100644 --- a/reflex/utils/console.py +++ b/reflex/utils/console.py @@ -2,6 +2,8 @@ from __future__ import annotations +import os + from rich.console import Console from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn from rich.prompt import Prompt @@ -12,7 +14,7 @@ from reflex.constants import LogLevel _console = Console() # The current log level. -_LOG_LEVEL = LogLevel.INFO +_LOG_LEVEL = LogLevel.DEFAULT # Deprecated features who's warning has been printed. _EMITTED_DEPRECATION_WARNINGS = set() @@ -61,6 +63,9 @@ def set_log_level(log_level: LogLevel): raise ValueError(f"Invalid log level: {log_level}") from ae global _LOG_LEVEL + if log_level != _LOG_LEVEL: + # Set the loglevel persistently for subprocesses + os.environ["LOGLEVEL"] = log_level.value _LOG_LEVEL = log_level diff --git a/scripts/integration.sh b/scripts/integration.sh index dc8b5d553..41d3051d0 100755 --- a/scripts/integration.sh +++ b/scripts/integration.sh @@ -34,4 +34,4 @@ if [ -f /proc/$pid/winpid ]; then echo "Windows detected, passing winpid $pid to port waiter" fi -python scripts/wait_for_listening_port.py $check_ports --timeout=900 --server-pid "$pid" +python scripts/wait_for_listening_port.py $check_ports --timeout=900 --server-pid "$pid" $WAIT_FOR_LISTENING_PORT_ARGS diff --git a/scripts/wait_for_listening_port.py b/scripts/wait_for_listening_port.py index 43581f0bc..71b6dd8b2 100644 --- a/scripts/wait_for_listening_port.py +++ b/scripts/wait_for_listening_port.py @@ -10,6 +10,8 @@ import time from concurrent.futures import ThreadPoolExecutor, as_completed from typing import Tuple +import httpx + # psutil is already a dependency of Reflex itself - so it's OK to use import psutil @@ -23,6 +25,7 @@ def _pid_exists(pid): return pid in psutil.pids() +# Not really used anymore now that we actually check the HTTP response. def _wait_for_port(port, server_pid, timeout) -> Tuple[bool, str]: start = time.time() print(f"Waiting for up to {timeout} seconds for port {port} to start listening.") # noqa: T201 @@ -41,25 +44,70 @@ def _wait_for_port(port, server_pid, timeout) -> Tuple[bool, str]: time.sleep(5) +def _wait_for_http_response(port, server_pid, timeout, path) -> Tuple[bool, str, str]: + start = time.time() + if path[0] != "/": + # This is a hack for passing the path on windows without a leading slash + # which mangles it https://stackoverflow.com/a/49013604 + path = "/" + path + url = f"http://localhost:{port}{path}" + print(f"Waiting for up to {timeout} seconds for {url} to return HTTP response.") # noqa: T201 + while True: + try: + if not _pid_exists(server_pid): + return False, f"Server PID {server_pid} is not running.", "" + response = httpx.get(url, timeout=0.5) + response.raise_for_status() + return ( + True, + f"{url} returned response after {time.time() - start} seconds", + response.text, + ) + except Exception as exc: # noqa: PERF203 + if time.time() - start > timeout: + return ( + False, + f"{url} still returning errors after {timeout} seconds: {exc!r}.", + "", + ) + time.sleep(5) + + def main(): """Wait for ports to start listening.""" parser = argparse.ArgumentParser(description="Wait for ports to start listening.") parser.add_argument("port", type=int, nargs="+") parser.add_argument("--timeout", type=int, required=True) parser.add_argument("--server-pid", type=int) + parser.add_argument("--path", type=str, default="/") args = parser.parse_args() + start = time.time() executor = ThreadPoolExecutor(max_workers=len(args.port)) futures = [ - executor.submit(_wait_for_port, p, args.server_pid, args.timeout) + executor.submit( + _wait_for_http_response, + p, + args.server_pid, + args.timeout, + args.path, + ) for p in args.port ] + base_content = None for f in as_completed(futures): - ok, msg = f.result() + ok, msg, content = f.result() if ok: print(f"OK: {msg}") # noqa: T201 + if base_content is None: + base_content = content + else: + assert ( + content == base_content + ), f"HTTP responses are not equal {content!r} != {base_content!r}." else: print(f"FAIL: {msg}") # noqa: T201 exit(1) + print(f"OK: All HTTP responses are equal after {time.time() - start} sec.") # noqa: T201 if __name__ == "__main__": diff --git a/tests/integration/init-test/in_docker_test_script.sh b/tests/integration/init-test/in_docker_test_script.sh index 31d245588..54f821ccf 100755 --- a/tests/integration/init-test/in_docker_test_script.sh +++ b/tests/integration/init-test/in_docker_test_script.sh @@ -29,7 +29,7 @@ source ~/venv/bin/activate pip install -U pip echo "Installing reflex from local repo code" -pip install /reflex-repo +pip install '/reflex-repo[proxy]' redis-server & From dcdcbfd83391314db93cb2cdbbf3cc12619d084e Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Fri, 3 Jan 2025 16:56:29 -0800 Subject: [PATCH 13/44] [ENG-2157] [Refix] Allow `rx.download` to resolve `rx.get_upload_url` (#4470) * [ENG-2157] [Refix] Allow `rx.download` to resolve `rx.get_upload_url` Update the special case in a way that works with the new Var system and its unstoppable determination to concatenate strings instead of using template string This has been broken since 0.6.0, so a regression test was added to avoid future inadvertant breakage. Fix #2812 (again) * use safer indirect eval --- reflex/.templates/web/utils/state.js | 13 ++- tests/integration/test_upload.py | 160 +++++++++++++++++++++------ 2 files changed, 138 insertions(+), 35 deletions(-) diff --git a/reflex/.templates/web/utils/state.js b/reflex/.templates/web/utils/state.js index 608df084a..41dbee446 100644 --- a/reflex/.templates/web/utils/state.js +++ b/reflex/.templates/web/utils/state.js @@ -208,11 +208,16 @@ export const applyEvent = async (event, socket) => { if (event.name == "_download") { const a = document.createElement("a"); a.hidden = true; + a.href = event.payload.url; // Special case when linking to uploaded files - a.href = event.payload.url.replace( - "${getBackendURL(env.UPLOAD)}", - getBackendURL(env.UPLOAD) - ); + if (a.href.includes("getBackendURL(env.UPLOAD)")) { + a.href = eval?.( + event.payload.url.replace( + "getBackendURL(env.UPLOAD)", + `"${getBackendURL(env.UPLOAD)}"` + ) + ); + } a.download = event.payload.filename; a.click(); a.remove(); diff --git a/tests/integration/test_upload.py b/tests/integration/test_upload.py index 156cf0e45..0331c15d6 100644 --- a/tests/integration/test_upload.py +++ b/tests/integration/test_upload.py @@ -6,12 +6,16 @@ import asyncio import time from pathlib import Path from typing import Generator +from urllib.parse import urlsplit import pytest from selenium.webdriver.common.by import By +from reflex.constants.event import Endpoint from reflex.testing import AppHarness, WebDriver +from .utils import poll_for_navigation + def UploadFile(): """App for testing dynamic routes.""" @@ -23,7 +27,7 @@ def UploadFile(): class UploadState(rx.State): _file_data: Dict[str, str] = {} - event_order: List[str] = [] + event_order: rx.Field[List[str]] = rx.field([]) progress_dicts: List[dict] = [] disabled: bool = False large_data: str = "" @@ -50,6 +54,15 @@ def UploadFile(): self.large_data = "" self.event_order.append("chain_event") + async def handle_upload_tertiary(self, files: List[rx.UploadFile]): + for file in files: + (rx.get_upload_dir() / (file.filename or "INVALID")).write_bytes( + await file.read() + ) + + def do_download(self): + return rx.download(rx.get_upload_url("test.txt")) + def index(): return rx.vstack( rx.input( @@ -123,6 +136,34 @@ def UploadFile(): on_click=rx.cancel_upload("secondary"), id="cancel_button_secondary", ), + rx.heading("Tertiary Upload/Download"), + rx.upload.root( + rx.vstack( + rx.button("Select File"), + rx.text("Drag and drop files here or click to select files"), + ), + id="tertiary", + ), + rx.button( + "Upload", + on_click=UploadState.handle_upload_tertiary( # type: ignore + rx.upload_files( + upload_id="tertiary", + ), + ), + id="upload_button_tertiary", + ), + rx.button( + "Download - Frontend", + on_click=rx.download(rx.get_upload_url("test.txt")), + id="download-frontend", + ), + rx.button( + "Download - Backend", + on_click=UploadState.do_download, + id="download-backend", + ), + rx.text(UploadState.event_order.to_string(), id="event-order"), ) app = rx.App(state=rx.State) @@ -164,6 +205,24 @@ def driver(upload_file: AppHarness): driver.quit() +def poll_for_token(driver: WebDriver, upload_file: AppHarness) -> str: + """Poll for the token input to be populated. + + Args: + driver: WebDriver instance. + upload_file: harness for UploadFile app. + + Returns: + token value + """ + token_input = driver.find_element(By.ID, "token") + assert token_input + # wait for the backend connection to send the token + token = upload_file.poll_for_value(token_input) + assert token is not None + return token + + @pytest.mark.parametrize("secondary", [False, True]) @pytest.mark.asyncio async def test_upload_file( @@ -178,11 +237,7 @@ async def test_upload_file( secondary: whether to use the secondary upload form """ assert upload_file.app_instance is not None - token_input = driver.find_element(By.ID, "token") - assert token_input - # wait for the backend connection to send the token - token = upload_file.poll_for_value(token_input) - assert token is not None + token = poll_for_token(driver, upload_file) full_state_name = upload_file.get_full_state_name(["_upload_state"]) state_name = upload_file.get_state_name("_upload_state") substate_token = f"{token}_{full_state_name}" @@ -204,6 +259,19 @@ async def test_upload_file( upload_box.send_keys(str(target_file)) upload_button.click() + # check that the selected files are displayed + selected_files = driver.find_element(By.ID, f"selected_files{suffix}") + assert Path(selected_files.text).name == Path(exp_name).name + + if secondary: + event_order_displayed = driver.find_element(By.ID, "event-order") + AppHarness._poll_for(lambda: "chain_event" in event_order_displayed.text) + + state = await upload_file.get_state(substate_token) + # only the secondary form tracks progress and chain events + assert state.substates[state_name].event_order.count("upload_progress") == 1 + assert state.substates[state_name].event_order.count("chain_event") == 1 + # look up the backend state and assert on uploaded contents async def get_file_data(): return ( @@ -217,16 +285,6 @@ async def test_upload_file( normalized_file_data = {Path(k).name: v for k, v in file_data.items()} assert normalized_file_data[Path(exp_name).name] == exp_contents - # check that the selected files are displayed - selected_files = driver.find_element(By.ID, f"selected_files{suffix}") - assert Path(selected_files.text).name == Path(exp_name).name - - state = await upload_file.get_state(substate_token) - if secondary: - # only the secondary form tracks progress and chain events - assert state.substates[state_name].event_order.count("upload_progress") == 1 - assert state.substates[state_name].event_order.count("chain_event") == 1 - @pytest.mark.asyncio async def test_upload_file_multiple(tmp_path, upload_file: AppHarness, driver): @@ -238,11 +296,7 @@ async def test_upload_file_multiple(tmp_path, upload_file: AppHarness, driver): driver: WebDriver instance. """ assert upload_file.app_instance is not None - token_input = driver.find_element(By.ID, "token") - assert token_input - # wait for the backend connection to send the token - token = upload_file.poll_for_value(token_input) - assert token is not None + token = poll_for_token(driver, upload_file) full_state_name = upload_file.get_full_state_name(["_upload_state"]) state_name = upload_file.get_state_name("_upload_state") substate_token = f"{token}_{full_state_name}" @@ -301,11 +355,7 @@ def test_clear_files( secondary: whether to use the secondary upload form. """ assert upload_file.app_instance is not None - token_input = driver.find_element(By.ID, "token") - assert token_input - # wait for the backend connection to send the token - token = upload_file.poll_for_value(token_input) - assert token is not None + poll_for_token(driver, upload_file) suffix = "_secondary" if secondary else "" @@ -357,11 +407,7 @@ async def test_cancel_upload(tmp_path, upload_file: AppHarness, driver: WebDrive driver: WebDriver instance. """ assert upload_file.app_instance is not None - token_input = driver.find_element(By.ID, "token") - assert token_input - # wait for the backend connection to send the token - token = upload_file.poll_for_value(token_input) - assert token is not None + token = poll_for_token(driver, upload_file) state_name = upload_file.get_state_name("_upload_state") state_full_name = upload_file.get_full_state_name(["_upload_state"]) substate_token = f"{token}_{state_full_name}" @@ -403,3 +449,55 @@ async def test_cancel_upload(tmp_path, upload_file: AppHarness, driver: WebDrive assert Path(exp_name).name not in normalized_file_data target_file.unlink() + + +@pytest.mark.asyncio +async def test_upload_download_file( + tmp_path, + upload_file: AppHarness, + driver: WebDriver, +): + """Submit a file upload and then fetch it with rx.download. + + This checks the special case `getBackendURL` logic in the _download event + handler in state.js. + + Args: + tmp_path: pytest tmp_path fixture + upload_file: harness for UploadFile app. + driver: WebDriver instance. + """ + assert upload_file.app_instance is not None + poll_for_token(driver, upload_file) + + upload_box = driver.find_elements(By.XPATH, "//input[@type='file']")[2] + assert upload_box + upload_button = driver.find_element(By.ID, "upload_button_tertiary") + assert upload_button + + exp_name = "test.txt" + exp_contents = "test file contents!" + target_file = tmp_path / exp_name + target_file.write_text(exp_contents) + + upload_box.send_keys(str(target_file)) + upload_button.click() + + # Download via event embedded in frontend code. + download_frontend = driver.find_element(By.ID, "download-frontend") + with poll_for_navigation(driver): + download_frontend.click() + assert urlsplit(driver.current_url).path == f"/{Endpoint.UPLOAD.value}/test.txt" + assert driver.find_element(by=By.TAG_NAME, value="body").text == exp_contents + + # Go back and wait for the app to reload. + with poll_for_navigation(driver): + driver.back() + poll_for_token(driver, upload_file) + + # Download via backend event handler. + download_backend = driver.find_element(By.ID, "download-backend") + with poll_for_navigation(driver): + download_backend.click() + assert urlsplit(driver.current_url).path == f"/{Endpoint.UPLOAD.value}/test.txt" + assert driver.find_element(by=By.TAG_NAME, value="body").text == exp_contents From ab4e05be345fdfbf98f4a5d94f825dc8031296cb Mon Sep 17 00:00:00 2001 From: Kanva Bhatia <56185464+KanvaBhatia@users.noreply.github.com> Date: Mon, 6 Jan 2025 04:23:39 +0530 Subject: [PATCH 14/44] fixes #4578 - correct the way dim_props created (#4587) --- reflex/components/recharts/charts.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reflex/components/recharts/charts.py b/reflex/components/recharts/charts.py index 85e10c2c5..bbe733244 100644 --- a/reflex/components/recharts/charts.py +++ b/reflex/components/recharts/charts.py @@ -85,8 +85,8 @@ class ChartBase(RechartsCharts): cls._ensure_valid_dimension("height", height) dim_props = { - "width": width or "100%", - "height": height or "100%", + "width": width if width is not None else "100%", + "height": height if height is not None else "100%", } # Provide min dimensions so the graph always appears, even if the outer container is zero-size. if width is None: From 59b3aaca426a817c963a2de8a9ab8037a4d40ed7 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Mon, 6 Jan 2025 11:17:03 -0800 Subject: [PATCH 15/44] Add deprecation message for non-cached var (#4591) DeprecationWarning: Default non-cached rx.var has been deprecated in version 0.6.8 the default value will be `@rx.var(cache=True)` in a future release. To retain uncached var, explicitly pass `@rx.var(cache=False)`. It will be completely removed in 0.7.0 --- reflex/vars/base.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/reflex/vars/base.py b/reflex/vars/base.py index 094a478c8..31c5f2e94 100644 --- a/reflex/vars/base.py +++ b/reflex/vars/base.py @@ -2276,7 +2276,7 @@ def computed_var( def computed_var( fget: Callable[[BASE_STATE], Any] | None = None, initial_value: Any | types.Unset = types.Unset(), - cache: bool = False, + cache: Optional[bool] = None, deps: Optional[List[Union[str, Var]]] = None, auto_deps: bool = True, interval: Optional[Union[datetime.timedelta, int]] = None, @@ -2302,6 +2302,15 @@ def computed_var( ValueError: If caching is disabled and an update interval is set. VarDependencyError: If user supplies dependencies without caching. """ + if cache is None: + cache = False + console.deprecate( + "Default non-cached rx.var", + "the default value will be `@rx.var(cache=True)` in a future release. " + "To retain uncached var, explicitly pass `@rx.var(cache=False)`", + deprecation_version="0.6.8", + removal_version="0.7.0", + ) if cache is False and interval is not None: raise ValueError("Cannot set update interval without caching.") From 9fafb6d5269121fbbb01d645d4f26d4f9711fa51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= Date: Mon, 6 Jan 2025 13:06:56 -0800 Subject: [PATCH 16/44] use position in vardata to mark internal hooks (#4549) * use position in vardata to mark internal hooks * update all render to use position * use macros for rendering * reduce number of iterations over hooks during rendering * cleanup code and add typing * add __future__ * use new macros to render component maps in markdown * remove calls to _get_all_hooks_internal * fix typo * forgot to replace this * unnecessary expand in utils.py --- .../.templates/jinja/web/pages/_app.js.jinja2 | 6 +-- .../web/pages/custom_component.js.jinja2 | 7 ++- .../jinja/web/pages/index.js.jinja2 | 5 +- .../jinja/web/pages/macros.js.jinja2 | 38 +++++++++++++ .../web/pages/stateful_component.js.jinja2 | 20 ++----- reflex/compiler/compiler.py | 4 +- reflex/compiler/templates.py | 41 ++++++++++++++ reflex/compiler/utils.py | 2 +- reflex/components/base/bare.py | 5 +- reflex/components/component.py | 53 ++++++++++++------- reflex/components/el/elements/forms.py | 4 +- reflex/components/markdown/markdown.py | 5 +- reflex/constants/compiler.py | 1 + reflex/experimental/client_state.py | 2 +- reflex/vars/base.py | 6 ++- 15 files changed, 140 insertions(+), 59 deletions(-) create mode 100644 reflex/.templates/jinja/web/pages/macros.js.jinja2 diff --git a/reflex/.templates/jinja/web/pages/_app.js.jinja2 b/reflex/.templates/jinja/web/pages/_app.js.jinja2 index 21cfd921a..40e31dee6 100644 --- a/reflex/.templates/jinja/web/pages/_app.js.jinja2 +++ b/reflex/.templates/jinja/web/pages/_app.js.jinja2 @@ -1,4 +1,5 @@ {% extends "web/pages/base_page.js.jinja2" %} +{% from "web/pages/macros.js.jinja2" import renderHooks %} {% block early_imports %} import '$/styles/styles.css' @@ -18,10 +19,7 @@ import * as {{library_alias}} from "{{library_path}}"; {% block export %} function AppWrap({children}) { - - {% for hook in hooks %} - {{ hook }} - {% endfor %} + {{ renderHooks(hooks) }} return ( {{utils.render(render, indent_width=0)}} diff --git a/reflex/.templates/jinja/web/pages/custom_component.js.jinja2 b/reflex/.templates/jinja/web/pages/custom_component.js.jinja2 index 222524d2d..e729d7273 100644 --- a/reflex/.templates/jinja/web/pages/custom_component.js.jinja2 +++ b/reflex/.templates/jinja/web/pages/custom_component.js.jinja2 @@ -1,5 +1,5 @@ {% extends "web/pages/base_page.js.jinja2" %} - +{% from "web/pages/macros.js.jinja2" import renderHooks %} {% block export %} {% for component in components %} @@ -8,9 +8,8 @@ {% endfor %} export const {{component.name}} = memo(({ {{-component.props|join(", ")-}} }) => { - {% for hook in component.hooks %} - {{ hook }} - {% endfor %} + {{ renderHooks(component.hooks) }} + return( {{utils.render(component.render)}} ) diff --git a/reflex/.templates/jinja/web/pages/index.js.jinja2 b/reflex/.templates/jinja/web/pages/index.js.jinja2 index efb086ef5..5551ad5fc 100644 --- a/reflex/.templates/jinja/web/pages/index.js.jinja2 +++ b/reflex/.templates/jinja/web/pages/index.js.jinja2 @@ -1,4 +1,5 @@ {% extends "web/pages/base_page.js.jinja2" %} +{% from "web/pages/macros.js.jinja2" import renderHooks %} {% block declaration %} {% for custom_code in custom_codes %} @@ -8,9 +9,7 @@ {% block export %} export default function Component() { - {% for hook in hooks %} - {{ hook }} - {% endfor %} + {{ renderHooks(hooks)}} return ( {{utils.render(render, indent_width=0)}} diff --git a/reflex/.templates/jinja/web/pages/macros.js.jinja2 b/reflex/.templates/jinja/web/pages/macros.js.jinja2 new file mode 100644 index 000000000..68810d896 --- /dev/null +++ b/reflex/.templates/jinja/web/pages/macros.js.jinja2 @@ -0,0 +1,38 @@ +{% macro renderHooks(hooks) %} + {% set sorted_hooks = sort_hooks(hooks) %} + + {# Render the grouped hooks #} + {% for hook, _ in sorted_hooks[const.hook_position.INTERNAL] %} + {{ hook }} + {% endfor %} + + {% for hook, _ in sorted_hooks[const.hook_position.PRE_TRIGGER] %} + {{ hook }} + {% endfor %} + + {% for hook, _ in sorted_hooks[const.hook_position.POST_TRIGGER] %} + {{ hook }} + {% endfor %} +{% endmacro %} + +{% macro renderHooksWithMemo(hooks, memo)%} + {% set sorted_hooks = sort_hooks(hooks) %} + + {# Render the grouped hooks #} + {% for hook, _ in sorted_hooks[const.hook_position.INTERNAL] %} + {{ hook }} + {% endfor %} + + {% for hook, _ in sorted_hooks[const.hook_position.PRE_TRIGGER] %} + {{ hook }} + {% endfor %} + + {% for hook in memo %} + {{ hook }} + {% endfor %} + + {% for hook, _ in sorted_hooks[const.hook_position.POST_TRIGGER] %} + {{ hook }} + {% endfor %} + +{% endmacro %} \ No newline at end of file diff --git a/reflex/.templates/jinja/web/pages/stateful_component.js.jinja2 b/reflex/.templates/jinja/web/pages/stateful_component.js.jinja2 index b04a78781..208a5755f 100644 --- a/reflex/.templates/jinja/web/pages/stateful_component.js.jinja2 +++ b/reflex/.templates/jinja/web/pages/stateful_component.js.jinja2 @@ -1,22 +1,10 @@ {% import 'web/pages/utils.js.jinja2' as utils %} +{% from 'web/pages/macros.js.jinja2' import renderHooksWithMemo %} +{% set all_hooks = component._get_all_hooks() %} export function {{tag_name}} () { - {% for hook in component._get_all_hooks_internal() %} - {{ hook }} - {% endfor %} - - {% for hook, data in component._get_all_hooks().items() if not data.position or data.position == const.hook_position.PRE_TRIGGER %} - {{ hook }} - {% endfor %} - - {% for hook in memo_trigger_hooks %} - {{ hook }} - {% endfor %} - - {% for hook, data in component._get_all_hooks().items() if data.position and data.position == const.hook_position.POST_TRIGGER %} - {{ hook }} - {% endfor %} - + {{ renderHooksWithMemo(all_hooks, memo_trigger_hooks) }} + return ( {{utils.render(component.render(), indent_width=0)}} ) diff --git a/reflex/compiler/compiler.py b/reflex/compiler/compiler.py index 9f81f319d..218dd0c55 100644 --- a/reflex/compiler/compiler.py +++ b/reflex/compiler/compiler.py @@ -75,7 +75,7 @@ def _compile_app(app_root: Component) -> str: return templates.APP_ROOT.render( imports=utils.compile_imports(app_root._get_all_imports()), custom_codes=app_root._get_all_custom_code(), - hooks={**app_root._get_all_hooks_internal(), **app_root._get_all_hooks()}, + hooks=app_root._get_all_hooks(), window_libraries=window_libraries, render=app_root.render(), ) @@ -149,7 +149,7 @@ def _compile_page( imports=imports, dynamic_imports=component._get_all_dynamic_imports(), custom_codes=component._get_all_custom_code(), - hooks={**component._get_all_hooks_internal(), **component._get_all_hooks()}, + hooks=component._get_all_hooks(), render=component.render(), **kwargs, ) diff --git a/reflex/compiler/templates.py b/reflex/compiler/templates.py index 631aa4ee2..117b655a9 100644 --- a/reflex/compiler/templates.py +++ b/reflex/compiler/templates.py @@ -1,9 +1,46 @@ """Templates to use in the reflex compiler.""" +from __future__ import annotations + from jinja2 import Environment, FileSystemLoader, Template from reflex import constants +from reflex.constants import Hooks from reflex.utils.format import format_state_name, json_dumps +from reflex.vars.base import VarData + + +def _sort_hooks(hooks: dict[str, VarData | None]): + """Sort the hooks by their position. + + Args: + hooks: The hooks to sort. + + Returns: + The sorted hooks. + """ + sorted_hooks = { + Hooks.HookPosition.INTERNAL: [], + Hooks.HookPosition.PRE_TRIGGER: [], + Hooks.HookPosition.POST_TRIGGER: [], + } + + for hook, data in hooks.items(): + if data and data.position and data.position == Hooks.HookPosition.INTERNAL: + sorted_hooks[Hooks.HookPosition.INTERNAL].append((hook, data)) + elif not data or ( + not data.position + or data.position == constants.Hooks.HookPosition.PRE_TRIGGER + ): + sorted_hooks[Hooks.HookPosition.PRE_TRIGGER].append((hook, data)) + elif ( + data + and data.position + and data.position == constants.Hooks.HookPosition.POST_TRIGGER + ): + sorted_hooks[Hooks.HookPosition.POST_TRIGGER].append((hook, data)) + + return sorted_hooks class ReflexJinjaEnvironment(Environment): @@ -47,6 +84,7 @@ class ReflexJinjaEnvironment(Environment): "frontend_exception_state": constants.CompileVars.FRONTEND_EXCEPTION_STATE_FULL, "hook_position": constants.Hooks.HookPosition, } + self.globals["sort_hooks"] = _sort_hooks def get_template(name: str) -> Template: @@ -103,6 +141,9 @@ STYLE = get_template("web/styles/styles.css.jinja2") # Code that generate the package json file PACKAGE_JSON = get_template("web/package.json.jinja2") +# Template containing some macros used in the web pages. +MACROS = get_template("web/pages/macros.js.jinja2") + # Code that generate the pyproject.toml file for custom components. CUSTOM_COMPONENTS_PYPROJECT_TOML = get_template( "custom_components/pyproject.toml.jinja2" diff --git a/reflex/compiler/utils.py b/reflex/compiler/utils.py index 1d698431c..c0ba28f4b 100644 --- a/reflex/compiler/utils.py +++ b/reflex/compiler/utils.py @@ -290,7 +290,7 @@ def compile_custom_component( "name": component.tag, "props": props, "render": render.render(), - "hooks": {**render._get_all_hooks_internal(), **render._get_all_hooks()}, + "hooks": render._get_all_hooks(), "custom_code": render._get_all_custom_code(), }, imports, diff --git a/reflex/components/base/bare.py b/reflex/components/base/bare.py index e1b5d9237..7cd225deb 100644 --- a/reflex/components/base/bare.py +++ b/reflex/components/base/bare.py @@ -9,6 +9,7 @@ from reflex.components.tags import Tag from reflex.components.tags.tagless import Tagless from reflex.utils.imports import ParsedImportDict from reflex.vars import BooleanVar, ObjectVar, Var +from reflex.vars.base import VarData class Bare(Component): @@ -32,7 +33,7 @@ class Bare(Component): contents = str(contents) if contents is not None else "" return cls(contents=contents) # type: ignore - def _get_all_hooks_internal(self) -> dict[str, None]: + def _get_all_hooks_internal(self) -> dict[str, VarData | None]: """Include the hooks for the component. Returns: @@ -43,7 +44,7 @@ class Bare(Component): hooks |= self.contents._var_value._get_all_hooks_internal() return hooks - def _get_all_hooks(self) -> dict[str, None]: + def _get_all_hooks(self) -> dict[str, VarData | None]: """Include the hooks for the component. Returns: diff --git a/reflex/components/component.py b/reflex/components/component.py index c1d4cbc80..46708e572 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -102,7 +102,7 @@ class BaseComponent(Base, ABC): """ @abstractmethod - def _get_all_hooks_internal(self) -> dict[str, None]: + def _get_all_hooks_internal(self) -> dict[str, VarData | None]: """Get the reflex internal hooks for the component and its children. Returns: @@ -110,7 +110,7 @@ class BaseComponent(Base, ABC): """ @abstractmethod - def _get_all_hooks(self) -> dict[str, None]: + def _get_all_hooks(self) -> dict[str, VarData | None]: """Get the React hooks for this component. Returns: @@ -1272,7 +1272,7 @@ class Component(BaseComponent, ABC): """ _imports = {} - if self._get_ref_hook(): + if self._get_ref_hook() is not None: # Handle hooks needed for attaching react refs to DOM nodes. _imports.setdefault("react", set()).add(ImportVar(tag="useRef")) _imports.setdefault(f"$/{Dirs.STATE_PATH}", set()).add( @@ -1388,7 +1388,7 @@ class Component(BaseComponent, ABC): }} }}, []);""" - def _get_ref_hook(self) -> str | None: + def _get_ref_hook(self) -> Var | None: """Generate the ref hook for the component. Returns: @@ -1396,11 +1396,12 @@ class Component(BaseComponent, ABC): """ ref = self.get_ref() if ref is not None: - return ( - f"const {ref} = useRef(null); {Var(_js_expr=ref)._as_ref()!s} = {ref};" + return Var( + f"const {ref} = useRef(null); {Var(_js_expr=ref)._as_ref()!s} = {ref};", + _var_data=VarData(position=Hooks.HookPosition.INTERNAL), ) - def _get_vars_hooks(self) -> dict[str, None]: + def _get_vars_hooks(self) -> dict[str, VarData | None]: """Get the hooks required by vars referenced in this component. Returns: @@ -1413,27 +1414,38 @@ class Component(BaseComponent, ABC): vars_hooks.update( var_data.hooks if isinstance(var_data.hooks, dict) - else {k: None for k in var_data.hooks} + else { + k: VarData(position=Hooks.HookPosition.INTERNAL) + for k in var_data.hooks + } ) return vars_hooks - def _get_events_hooks(self) -> dict[str, None]: + def _get_events_hooks(self) -> dict[str, VarData | None]: """Get the hooks required by events referenced in this component. Returns: The hooks for the events. """ - return {Hooks.EVENTS: None} if self.event_triggers else {} + return ( + {Hooks.EVENTS: VarData(position=Hooks.HookPosition.INTERNAL)} + if self.event_triggers + else {} + ) - def _get_special_hooks(self) -> dict[str, None]: + def _get_special_hooks(self) -> dict[str, VarData | None]: """Get the hooks required by special actions referenced in this component. Returns: The hooks for special actions. """ - return {Hooks.AUTOFOCUS: None} if self.autofocus else {} + return ( + {Hooks.AUTOFOCUS: VarData(position=Hooks.HookPosition.INTERNAL)} + if self.autofocus + else {} + ) - def _get_hooks_internal(self) -> dict[str, None]: + def _get_hooks_internal(self) -> dict[str, VarData | None]: """Get the React hooks for this component managed by the framework. Downstream components should NOT override this method to avoid breaking @@ -1444,7 +1456,7 @@ class Component(BaseComponent, ABC): """ return { **{ - hook: None + str(hook): VarData(position=Hooks.HookPosition.INTERNAL) for hook in [self._get_ref_hook(), self._get_mount_lifecycle_hook()] if hook is not None }, @@ -1493,7 +1505,7 @@ class Component(BaseComponent, ABC): """ return - def _get_all_hooks_internal(self) -> dict[str, None]: + def _get_all_hooks_internal(self) -> dict[str, VarData | None]: """Get the reflex internal hooks for the component and its children. Returns: @@ -1508,7 +1520,7 @@ class Component(BaseComponent, ABC): return code - def _get_all_hooks(self) -> dict[str, None]: + def _get_all_hooks(self) -> dict[str, VarData | None]: """Get the React hooks for this component and its children. Returns: @@ -1516,6 +1528,9 @@ class Component(BaseComponent, ABC): """ code = {} + # Add the internal hooks for this component. + code.update(self._get_hooks_internal()) + # Add the hook code for this component. hooks = self._get_hooks() if hooks is not None: @@ -2211,7 +2226,7 @@ class StatefulComponent(BaseComponent): ) return trigger_memo - def _get_all_hooks_internal(self) -> dict[str, None]: + def _get_all_hooks_internal(self) -> dict[str, VarData | None]: """Get the reflex internal hooks for the component and its children. Returns: @@ -2219,7 +2234,7 @@ class StatefulComponent(BaseComponent): """ return {} - def _get_all_hooks(self) -> dict[str, None]: + def _get_all_hooks(self) -> dict[str, VarData | None]: """Get the React hooks for this component. Returns: @@ -2337,7 +2352,7 @@ class MemoizationLeaf(Component): The memoization leaf """ comp = super().create(*children, **props) - if comp._get_all_hooks() or comp._get_all_hooks_internal(): + if comp._get_all_hooks(): comp._memoization_mode = cls._memoization_mode.copy( update={"disposition": MemoizationDisposition.ALWAYS} ) diff --git a/reflex/components/el/elements/forms.py b/reflex/components/el/elements/forms.py index 61ded4fd3..529a5e884 100644 --- a/reflex/components/el/elements/forms.py +++ b/reflex/components/el/elements/forms.py @@ -182,9 +182,7 @@ class Form(BaseHTML): props["handle_submit_unique_name"] = "" form = super().create(*children, **props) form.handle_submit_unique_name = md5( - str({**form._get_all_hooks_internal(), **form._get_all_hooks()}).encode( - "utf-8" - ) + str(form._get_all_hooks()).encode("utf-8") ).hexdigest() return form diff --git a/reflex/components/markdown/markdown.py b/reflex/components/markdown/markdown.py index cd82d5903..7c65c0d43 100644 --- a/reflex/components/markdown/markdown.py +++ b/reflex/components/markdown/markdown.py @@ -420,11 +420,12 @@ const {_LANGUAGE!s} = match ? match[1] : ''; def _get_custom_code(self) -> str | None: hooks = {} + from reflex.compiler.templates import MACROS + for _component in self.component_map.values(): comp = _component(_MOCK_ARG) - hooks.update(comp._get_all_hooks_internal()) hooks.update(comp._get_all_hooks()) - formatted_hooks = "\n".join(hooks.keys()) + formatted_hooks = MACROS.module.renderHooks(hooks) # type: ignore return f""" function {self._get_component_map_name()} () {{ {formatted_hooks} diff --git a/reflex/constants/compiler.py b/reflex/constants/compiler.py index 7ca55f4dd..d98c04d76 100644 --- a/reflex/constants/compiler.py +++ b/reflex/constants/compiler.py @@ -135,6 +135,7 @@ class Hooks(SimpleNamespace): class HookPosition(enum.Enum): """The position of the hook in the component.""" + INTERNAL = "internal" PRE_TRIGGER = "pre_trigger" POST_TRIGGER = "post_trigger" diff --git a/reflex/experimental/client_state.py b/reflex/experimental/client_state.py index 1982b3dfe..e37ceb14c 100644 --- a/reflex/experimental/client_state.py +++ b/reflex/experimental/client_state.py @@ -105,7 +105,7 @@ class ClientStateVar(Var): else: default_var = default setter_name = f"set{var_name.capitalize()}" - hooks = { + hooks: dict[str, VarData | None] = { f"const [{var_name}, {setter_name}] = useState({default_var!s})": None, } imports = { diff --git a/reflex/vars/base.py b/reflex/vars/base.py index 31c5f2e94..a4496d77d 100644 --- a/reflex/vars/base.py +++ b/reflex/vars/base.py @@ -127,7 +127,7 @@ class VarData: state: str = "", field_name: str = "", imports: ImportDict | ParsedImportDict | None = None, - hooks: dict[str, None] | None = None, + hooks: dict[str, VarData | None] | None = None, deps: list[Var] | None = None, position: Hooks.HookPosition | None = None, ): @@ -194,7 +194,9 @@ class VarData: (var_data.state for var_data in all_var_datas if var_data.state), "" ) - hooks = {hook: None for var_data in all_var_datas for hook in var_data.hooks} + hooks: dict[str, VarData | None] = { + hook: None for var_data in all_var_datas for hook in var_data.hooks + } _imports = imports.merge_imports( *(var_data.imports for var_data in all_var_datas) From eae15e3a83efa9552a2c5f4bfa5305fb3586333c Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Mon, 6 Jan 2025 14:35:45 -0800 Subject: [PATCH 17/44] poetry 2.0.0 compatibility (#4593) Remove `packages` key, since this project uses the "default" package layout, so this key isn't needed. --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index e50ef88bb..3e76ec5b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,6 @@ repository = "https://github.com/reflex-dev/reflex" documentation = "https://reflex.dev/docs/getting-started/introduction" keywords = ["web", "framework"] classifiers = ["Development Status :: 4 - Beta"] -packages = [{ include = "reflex" }] [tool.poetry.dependencies] python = "^3.9" From 72d7616726d32c1ef786d00aa4bb9467e5a3d0a2 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Tue, 7 Jan 2025 10:24:43 -0800 Subject: [PATCH 18/44] Enable automatic retry on redis errors (#4595) * Enable automatic retry on redis errors ExponentialBackoff 3x retry for BusyLoadingError, ConnectionError, and TimeoutError * retry on any redis error * Use default single-retry for any RedisError Using the default Retry means that async and sync clients get the appropriate type of Retry --- reflex/utils/prerequisites.py | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index 797d28701..d838c0eea 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -28,8 +28,8 @@ import typer from alembic.util.exc import CommandError from packaging import version from redis import Redis as RedisSync -from redis import exceptions from redis.asyncio import Redis +from redis.exceptions import RedisError from reflex import constants, model from reflex.compiler import templates @@ -333,10 +333,11 @@ def get_redis() -> Redis | None: Returns: The asynchronous redis client. """ - if isinstance((redis_url_or_options := parse_redis_url()), str): - return Redis.from_url(redis_url_or_options) - elif isinstance(redis_url_or_options, dict): - return Redis(**redis_url_or_options) + if (redis_url := parse_redis_url()) is not None: + return Redis.from_url( + redis_url, + retry_on_error=[RedisError], + ) return None @@ -346,14 +347,15 @@ def get_redis_sync() -> RedisSync | None: Returns: The synchronous redis client. """ - if isinstance((redis_url_or_options := parse_redis_url()), str): - return RedisSync.from_url(redis_url_or_options) - elif isinstance(redis_url_or_options, dict): - return RedisSync(**redis_url_or_options) + if (redis_url := parse_redis_url()) is not None: + return RedisSync.from_url( + redis_url, + retry_on_error=[RedisError], + ) return None -def parse_redis_url() -> str | dict | None: +def parse_redis_url() -> str | None: """Parse the REDIS_URL in config if applicable. Returns: @@ -387,7 +389,7 @@ async def get_redis_status() -> dict[str, bool | None]: redis_client.ping() else: status = None - except exceptions.RedisError: + except RedisError: status = False return {"redis": status} From 08d9fbf9bc5846c4f60737abcd8d8bf1d134ad96 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Tue, 7 Jan 2025 10:35:36 -0800 Subject: [PATCH 19/44] unbreak link _hover (#4537) * unbreak link _hover * add a test to catch the error * change tmp path for harness * add () to fixture * add spacer to avoid initial hover * only install chromium browser for faster ci --------- Co-authored-by: Lendemor --- .github/workflows/integration_app_harness.yml | 2 +- .../radix/themes/typography/link.py | 2 +- .../tests_playwright/test_link_hover.py | 46 +++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) create mode 100644 tests/integration/tests_playwright/test_link_hover.py diff --git a/.github/workflows/integration_app_harness.yml b/.github/workflows/integration_app_harness.yml index e6ea79377..21b021ee5 100644 --- a/.github/workflows/integration_app_harness.yml +++ b/.github/workflows/integration_app_harness.yml @@ -53,7 +53,7 @@ jobs: SCREENSHOT_DIR: /tmp/screenshots/${{ matrix.state_manager }}/${{ matrix.python-version }}/${{ matrix.split_index }} REDIS_URL: ${{ matrix.state_manager == 'redis' && 'redis://localhost:6379' || '' }} run: | - poetry run playwright install --with-deps + poetry run playwright install chromium poetry run pytest tests/integration --splits 2 --group ${{matrix.split_index}} - uses: actions/upload-artifact@v4 name: Upload failed test screenshots diff --git a/reflex/components/radix/themes/typography/link.py b/reflex/components/radix/themes/typography/link.py index 25a0902cc..c93102408 100644 --- a/reflex/components/radix/themes/typography/link.py +++ b/reflex/components/radix/themes/typography/link.py @@ -76,7 +76,7 @@ class Link(RadixThemesComponent, A, MemoizationLeaf, MarkdownComponentMap): Returns: Component: The link component """ - props.setdefault(":hover", {"color": color("accent", 8)}) + props.setdefault("_hover", {"color": color("accent", 8)}) href = props.get("href") is_external = props.pop("is_external", None) diff --git a/tests/integration/tests_playwright/test_link_hover.py b/tests/integration/tests_playwright/test_link_hover.py new file mode 100644 index 000000000..9510bd358 --- /dev/null +++ b/tests/integration/tests_playwright/test_link_hover.py @@ -0,0 +1,46 @@ +from typing import Generator + +import pytest +from playwright.sync_api import Page, expect + +from reflex.testing import AppHarness + + +def LinkApp(): + import reflex as rx + + app = rx.App() + + def index(): + return rx.vstack( + rx.box(height="10em"), # spacer, so the link isn't hovered initially + rx.link( + "Click me", + href="#", + color="blue", + _hover=rx.Style({"color": "red"}), + ), + ) + + app.add_page(index, "/") + + +@pytest.fixture() +def link_app(tmp_path_factory) -> Generator[AppHarness, None, None]: + with AppHarness.create( + root=tmp_path_factory.mktemp("link_app"), + app_source=LinkApp, # type: ignore + ) as harness: + assert harness.app_instance is not None, "app is not running" + yield harness + + +def test_link_hover(link_app: AppHarness, page: Page): + assert link_app.frontend_url is not None + page.goto(link_app.frontend_url) + + link = page.get_by_role("link") + expect(link).to_have_text("Click me") + expect(link).to_have_css("color", "rgb(0, 0, 255)") + link.hover() + expect(link).to_have_css("color", "rgb(255, 0, 0)") From 5f169bc88410ecaf9d2b6c3803dd42f8762b3b46 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Tue, 7 Jan 2025 13:20:10 -0800 Subject: [PATCH 20/44] test_lifespan: stop periodic events (#4600) avoid lingering events after getting the information we came for --- tests/integration/test_lifespan.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/tests/integration/test_lifespan.py b/tests/integration/test_lifespan.py index cb6c640ab..0fa4a7e92 100644 --- a/tests/integration/test_lifespan.py +++ b/tests/integration/test_lifespan.py @@ -43,6 +43,8 @@ def LifespanApp(): lifespan_task_global = 0 class LifespanState(rx.State): + interval: int = 100 + @rx.var def task_global(self) -> int: return lifespan_task_global @@ -59,7 +61,15 @@ def LifespanApp(): return rx.vstack( rx.text(LifespanState.task_global, id="task_global"), rx.text(LifespanState.context_global, id="context_global"), - rx.moment(interval=100, on_change=LifespanState.tick), + rx.button( + rx.moment( + interval=LifespanState.interval, on_change=LifespanState.tick + ), + on_click=LifespanState.set_interval( # type: ignore + rx.cond(LifespanState.interval, 0, 100) + ), + id="toggle-tick", + ), ) app = rx.App() @@ -108,6 +118,7 @@ async def test_lifespan(lifespan_app: AppHarness): original_task_global_text = task_global.text original_task_global_value = int(original_task_global_text) lifespan_app.poll_for_content(task_global, exp_not_equal=original_task_global_text) + driver.find_element(By.ID, "toggle-tick").click() # avoid teardown errors assert lifespan_app.app_module.lifespan_task_global > original_task_global_value # type: ignore assert int(task_global.text) > original_task_global_value From 93245ef143a500ac15ad100ae7f1512b35728f47 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Tue, 7 Jan 2025 13:20:25 -0800 Subject: [PATCH 21/44] [ENG-4255] Code blocks lead to redefined const in web page (#4598) Instead of potentially defining `_LANGUAGE` constant twice in a component, simply pass the language prop directly to the hook generator function. If no language is passed, then it defaults to `_LANGUAGE`, which continues to work for markdown component_map use case. --- reflex/components/datadisplay/code.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/reflex/components/datadisplay/code.py b/reflex/components/datadisplay/code.py index 2d5dfc625..8a433c18c 100644 --- a/reflex/components/datadisplay/code.py +++ b/reflex/components/datadisplay/code.py @@ -502,8 +502,8 @@ class CodeBlock(Component, MarkdownComponentMap): theme = self.theme - out.add_props(style=theme).remove_props("theme", "code", "language").add_props( - children=self.code, language=_LANGUAGE + out.add_props(style=theme).remove_props("theme", "code").add_props( + children=self.code, ) return out @@ -512,20 +512,25 @@ class CodeBlock(Component, MarkdownComponentMap): return ["can_copy", "copy_button"] @classmethod - def _get_language_registration_hook(cls) -> str: + def _get_language_registration_hook(cls, language_var: Var = _LANGUAGE) -> str: """Get the hook to register the language. + Args: + language_var: The const/literal Var of the language module to import. + For markdown, uses the default placeholder _LANGUAGE. For direct use, + a LiteralStringVar should be passed via the language prop. + Returns: The hook to register the language. """ return f""" - if ({_LANGUAGE!s}) {{ + if ({language_var!s}) {{ (async () => {{ try {{ - const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${{{_LANGUAGE!s}}}`); - SyntaxHighlighter.registerLanguage({_LANGUAGE!s}, module.default); + const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${{{language_var!s}}}`); + SyntaxHighlighter.registerLanguage({language_var!s}, module.default); }} catch (error) {{ - console.error(`Error importing language module for ${{{_LANGUAGE!s}}}:`, error); + console.error(`Error importing language module for ${{{language_var!s}}}:`, error); }} }})(); }} @@ -547,8 +552,7 @@ class CodeBlock(Component, MarkdownComponentMap): The hooks for the component. """ return [ - f"const {_LANGUAGE!s} = {self.language!s}", - self._get_language_registration_hook(), + self._get_language_registration_hook(language_var=self.language), ] From 880975ae94ca2c9a340b4c75ca00e80bafde0554 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Tue, 7 Jan 2025 14:41:03 -0800 Subject: [PATCH 22/44] improve client state (#4597) * improve client state * no comma * update python for unit tests * overwrite it for windows * bump other python versions --- .github/workflows/benchmarks.yml | 42 +++--- .github/workflows/check_generated_pyi.yml | 10 +- .github/workflows/check_node_latest.yml | 67 +++++---- .../workflows/check_outdated_dependencies.yml | 130 +++++++++--------- .github/workflows/integration_app_harness.yml | 4 +- .github/workflows/integration_tests.yml | 39 +++--- .github/workflows/pre-commit.yml | 6 +- .github/workflows/unit_tests.yml | 26 ++-- reflex/experimental/client_state.py | 64 ++++++--- 9 files changed, 205 insertions(+), 183 deletions(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index f743b7cbd..cbc34fff9 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -5,7 +5,7 @@ on: types: - closed paths-ignore: - - '**/*.md' + - "**/*.md" permissions: contents: read @@ -15,21 +15,21 @@ defaults: shell: bash env: - PYTHONIOENCODING: 'utf8' + PYTHONIOENCODING: "utf8" TELEMETRY_ENABLED: false - NODE_OPTIONS: '--max_old_space_size=8192' + NODE_OPTIONS: "--max_old_space_size=8192" PR_TITLE: ${{ github.event.pull_request.title }} jobs: reflex-web: -# if: github.event.pull_request.merged == true + # if: github.event.pull_request.merged == true strategy: fail-fast: false matrix: # Show OS combos first in GUI os: [ubuntu-latest] - python-version: ['3.11.4'] - node-version: ['18.x'] + python-version: ["3.12.8"] + node-version: ["18.x"] runs-on: ${{ matrix.os }} steps: @@ -81,24 +81,24 @@ jobs: matrix: # Show OS combos first in GUI os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ['3.9.18', '3.10.13', '3.11.5', '3.12.0'] + python-version: ["3.9.21", "3.10.16", "3.11.11", "3.12.8"] exclude: - os: windows-latest - python-version: '3.10.13' + python-version: "3.10.16" - os: windows-latest - python-version: '3.9.18' + python-version: "3.9.21" # keep only one python version for MacOS - os: macos-latest - python-version: '3.9.18' + python-version: "3.9.21" - os: macos-latest - python-version: '3.10.13' + python-version: "3.10.16" - os: macos-latest - python-version: '3.12.0' + python-version: "3.11.11" include: - os: windows-latest - python-version: '3.10.11' + python-version: "3.10.11" - os: windows-latest - python-version: '3.9.13' + python-version: "3.9.13" runs-on: ${{ matrix.os }} steps: @@ -123,7 +123,7 @@ jobs: --event-type "${{ github.event_name }}" --pr-id "${{ github.event.pull_request.id }}" reflex-dist-size: # This job is used to calculate the size of the Reflex distribution (wheel file) - if: github.event.pull_request.merged == true + if: github.event.pull_request.merged == true timeout-minutes: 30 strategy: # Prioritize getting more information out of the workflow (even if something fails) @@ -133,7 +133,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/setup_build_env with: - python-version: 3.11.5 + python-version: 3.12.8 run-poetry-install: true create-venv-at-path: .venv - name: Build reflex @@ -143,12 +143,12 @@ jobs: # Only run if the database creds are available in this context. run: poetry run python benchmarks/benchmark_package_size.py --os ubuntu-latest - --python-version 3.11.5 --commit-sha "${{ github.sha }}" --pr-id "${{ github.event.pull_request.id }}" + --python-version 3.12.8 --commit-sha "${{ github.sha }}" --pr-id "${{ github.event.pull_request.id }}" --branch-name "${{ github.head_ref || github.ref_name }}" --path ./dist reflex-venv-size: # This job calculates the total size of Reflex and its dependencies - if: github.event.pull_request.merged == true + if: github.event.pull_request.merged == true timeout-minutes: 30 strategy: # Prioritize getting more information out of the workflow (even if something fails) @@ -156,7 +156,7 @@ jobs: matrix: # Show OS combos first in GUI os: [ubuntu-latest, windows-latest, macos-latest] - python-version: ['3.11.5'] + python-version: ["3.12.8"] runs-on: ${{ matrix.os }} steps: @@ -186,6 +186,6 @@ jobs: run: poetry run python benchmarks/benchmark_package_size.py --os "${{ matrix.os }}" --python-version "${{ matrix.python-version }}" --commit-sha "${{ github.sha }}" - --pr-id "${{ github.event.pull_request.id }}" + --pr-id "${{ github.event.pull_request.id }}" --branch-name "${{ github.head_ref || github.ref_name }}" - --path ./.venv \ No newline at end of file + --path ./.venv diff --git a/.github/workflows/check_generated_pyi.yml b/.github/workflows/check_generated_pyi.yml index d9a0e8e71..760707d15 100644 --- a/.github/workflows/check_generated_pyi.yml +++ b/.github/workflows/check_generated_pyi.yml @@ -6,16 +6,16 @@ concurrency: on: push: - branches: ['main'] + branches: ["main"] # We don't just trigger on make_pyi.py and the components dir, because # there are other things that can change the generator output # e.g. black version, reflex.Component, reflex.Var. paths-ignore: - - '**/*.md' + - "**/*.md" pull_request: - branches: ['main'] + branches: ["main"] paths-ignore: - - '**/*.md' + - "**/*.md" jobs: check-generated-pyi-components: @@ -25,7 +25,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/setup_build_env with: - python-version: '3.11.5' + python-version: "3.12.8" run-poetry-install: true create-venv-at-path: .venv - run: | diff --git a/.github/workflows/check_node_latest.yml b/.github/workflows/check_node_latest.yml index 1cf9f6fdf..1957f64f8 100644 --- a/.github/workflows/check_node_latest.yml +++ b/.github/workflows/check_node_latest.yml @@ -1,43 +1,40 @@ name: integration-node-latest on: - push: - branches: - - main - pull_request: - branches: - - main + push: + branches: + - main + pull_request: + branches: + - main env: - TELEMETRY_ENABLED: false - REFLEX_USE_SYSTEM_NODE: true + TELEMETRY_ENABLED: false + REFLEX_USE_SYSTEM_NODE: true jobs: - check_latest_node: - runs-on: ubuntu-22.04 - strategy: - matrix: - python-version: ['3.12'] - split_index: [1, 2] - node-version: ['node'] - fail-fast: false - - steps: - - uses: actions/checkout@v4 - - uses: ./.github/actions/setup_build_env - with: - python-version: ${{ matrix.python-version }} - run-poetry-install: true - create-venv-at-path: .venv - - uses: actions/setup-node@v4 - with: - node-version: ${{ matrix.node-version }} - - run: | - poetry run uv pip install pyvirtualdisplay pillow pytest-split - poetry run playwright install --with-deps - - run: | - poetry run pytest tests/test_node_version.py - poetry run pytest tests/integration --splits 2 --group ${{matrix.split_index}} - - + check_latest_node: + runs-on: ubuntu-22.04 + strategy: + matrix: + python-version: ["3.12.8"] + split_index: [1, 2] + node-version: ["node"] + fail-fast: false + steps: + - uses: actions/checkout@v4 + - uses: ./.github/actions/setup_build_env + with: + python-version: ${{ matrix.python-version }} + run-poetry-install: true + create-venv-at-path: .venv + - uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + - run: | + poetry run uv pip install pyvirtualdisplay pillow pytest-split + poetry run playwright install --with-deps + - run: | + poetry run pytest tests/test_node_version.py + poetry run pytest tests/integration --splits 2 --group ${{matrix.split_index}} diff --git a/.github/workflows/check_outdated_dependencies.yml b/.github/workflows/check_outdated_dependencies.yml index a7465defb..30e048912 100644 --- a/.github/workflows/check_outdated_dependencies.yml +++ b/.github/workflows/check_outdated_dependencies.yml @@ -1,88 +1,86 @@ name: check-outdated-dependencies on: - push: # This will trigger the action when a pull request is opened or updated. + push: # This will trigger the action when a pull request is opened or updated. branches: - - 'release/**' # This will trigger the action when any branch starting with "release/" is created. - workflow_dispatch: # Allow manual triggering if needed. + - "release/**" # This will trigger the action when any branch starting with "release/" is created. + workflow_dispatch: # Allow manual triggering if needed. jobs: backend: runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@v3 + - name: Checkout code + uses: actions/checkout@v3 - - uses: ./.github/actions/setup_build_env - with: - python-version: '3.9' - run-poetry-install: true - create-venv-at-path: .venv + - uses: ./.github/actions/setup_build_env + with: + python-version: "3.9.21" + run-poetry-install: true + create-venv-at-path: .venv - - name: Check outdated backend dependencies - run: | - outdated=$(poetry show -oT) - echo "Outdated:" - echo "$outdated" + - name: Check outdated backend dependencies + run: | + outdated=$(poetry show -oT) + echo "Outdated:" + echo "$outdated" - filtered_outdated=$(echo "$outdated" | grep -vE 'pyright|ruff' || true) - - if [ ! -z "$filtered_outdated" ]; then - echo "Outdated dependencies found:" - echo "$filtered_outdated" - exit 1 - else - echo "All dependencies are up to date. (pyright and ruff are ignored)" - fi + filtered_outdated=$(echo "$outdated" | grep -vE 'pyright|ruff' || true) + if [ ! -z "$filtered_outdated" ]; then + echo "Outdated dependencies found:" + echo "$filtered_outdated" + exit 1 + else + echo "All dependencies are up to date. (pyright and ruff are ignored)" + fi frontend: runs-on: ubuntu-latest steps: - - name: Checkout code - uses: actions/checkout@v4 - - uses: ./.github/actions/setup_build_env - with: - python-version: '3.10.11' - run-poetry-install: true - create-venv-at-path: .venv - - name: Clone Reflex Website Repo - uses: actions/checkout@v4 - with: - repository: reflex-dev/reflex-web - ref: main - path: reflex-web - - name: Install Requirements for reflex-web - working-directory: ./reflex-web - run: poetry run uv pip install -r requirements.txt - - name: Install additional dependencies for DB access - run: poetry run uv pip install psycopg - - name: Init Website for reflex-web - working-directory: ./reflex-web - run: poetry run reflex init - - name: Run Website and Check for errors - run: | - poetry run bash scripts/integration.sh ./reflex-web dev - - name: Check outdated frontend dependencies - working-directory: ./reflex-web/.web - run: | - raw_outdated=$(/home/runner/.local/share/reflex/bun/bin/bun outdated) - outdated=$(echo "$raw_outdated" | grep -vE '\|\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\|' || true) - echo "Outdated:" - echo "$outdated" + - name: Checkout code + uses: actions/checkout@v4 + - uses: ./.github/actions/setup_build_env + with: + python-version: "3.10.16" + run-poetry-install: true + create-venv-at-path: .venv + - name: Clone Reflex Website Repo + uses: actions/checkout@v4 + with: + repository: reflex-dev/reflex-web + ref: main + path: reflex-web + - name: Install Requirements for reflex-web + working-directory: ./reflex-web + run: poetry run uv pip install -r requirements.txt + - name: Install additional dependencies for DB access + run: poetry run uv pip install psycopg + - name: Init Website for reflex-web + working-directory: ./reflex-web + run: poetry run reflex init + - name: Run Website and Check for errors + run: | + poetry run bash scripts/integration.sh ./reflex-web dev + - name: Check outdated frontend dependencies + working-directory: ./reflex-web/.web + run: | + raw_outdated=$(/home/runner/.local/share/reflex/bun/bin/bun outdated) + outdated=$(echo "$raw_outdated" | grep -vE '\|\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\|' || true) + echo "Outdated:" + echo "$outdated" - # Ignore 3rd party dependencies that are not updated. - filtered_outdated=$(echo "$outdated" | grep -vE 'Package|@chakra-ui|lucide-react|@splinetool/runtime|ag-grid-react|framer-motion|react-markdown|remark-math|remark-gfm|rehype-katex|rehype-raw|remark-unwrap-images' || true) - no_extra=$(echo "$filtered_outdated" | grep -vE '\|\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-' || true) + # Ignore 3rd party dependencies that are not updated. + filtered_outdated=$(echo "$outdated" | grep -vE 'Package|@chakra-ui|lucide-react|@splinetool/runtime|ag-grid-react|framer-motion|react-markdown|remark-math|remark-gfm|rehype-katex|rehype-raw|remark-unwrap-images' || true) + no_extra=$(echo "$filtered_outdated" | grep -vE '\|\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-\-' || true) - if [ ! -z "$no_extra" ]; then - echo "Outdated dependencies found:" - echo "$filtered_outdated" - exit 1 - else - echo "All dependencies are up to date. (3rd party packages are ignored)" - fi - + if [ ! -z "$no_extra" ]; then + echo "Outdated dependencies found:" + echo "$filtered_outdated" + exit 1 + else + echo "All dependencies are up to date. (3rd party packages are ignored)" + fi diff --git a/.github/workflows/integration_app_harness.yml b/.github/workflows/integration_app_harness.yml index 21b021ee5..6148ecd1a 100644 --- a/.github/workflows/integration_app_harness.yml +++ b/.github/workflows/integration_app_harness.yml @@ -22,8 +22,8 @@ jobs: timeout-minutes: 30 strategy: matrix: - state_manager: ['redis', 'memory'] - python-version: ['3.11.5', '3.12.0', '3.13.0'] + state_manager: ["redis", "memory"] + python-version: ["3.11.11", "3.12.8", "3.13.1"] split_index: [1, 2] fail-fast: false runs-on: ubuntu-22.04 diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 6c79c27c9..dda9e0211 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -2,13 +2,13 @@ name: integration-tests on: push: - branches: ['main'] + branches: ["main"] paths-ignore: - - '**/*.md' + - "**/*.md" pull_request: - branches: ['main'] + branches: ["main"] paths-ignore: - - '**/*.md' + - "**/*.md" concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.id }} @@ -27,9 +27,9 @@ env: # TODO: can we fix windows encoding natively within reflex? Bug above can hit real users too (less common, but possible) # - Catch encoding errors when printing logs # - Best effort print lines that contain illegal chars (map to some default char, etc.) - PYTHONIOENCODING: 'utf8' + PYTHONIOENCODING: "utf8" TELEMETRY_ENABLED: false - NODE_OPTIONS: '--max_old_space_size=8192' + NODE_OPTIONS: "--max_old_space_size=8192" PR_TITLE: ${{ github.event.pull_request.title }} jobs: @@ -43,17 +43,22 @@ jobs: matrix: # Show OS combos first in GUI os: [ubuntu-latest, windows-latest] - python-version: ['3.9.18', '3.10.13', '3.11.5', '3.12.0', '3.13.0'] + python-version: ["3.9.21", "3.10.16", "3.11.11", "3.12.8", "3.13.1"] + # Windows is a bit behind on Python version availability in Github exclude: - os: windows-latest - python-version: '3.10.13' + python-version: "3.11.11" - os: windows-latest - python-version: '3.9.18' + python-version: "3.10.16" + - os: windows-latest + python-version: "3.9.21" include: - os: windows-latest - python-version: '3.10.11' + python-version: "3.11.9" - os: windows-latest - python-version: '3.9.13' + python-version: "3.10.11" + - os: windows-latest + python-version: "3.9.13" runs-on: ${{ matrix.os }} steps: @@ -117,18 +122,16 @@ jobs: --branch-name "${{ github.head_ref || github.ref_name }}" --pr-id "${{ github.event.pull_request.id }}" --app-name "counter" - - reflex-web: strategy: fail-fast: false matrix: # Show OS combos first in GUI os: [ubuntu-latest] - python-version: ['3.10.11', '3.11.4'] + python-version: ["3.11.11", "3.12.8"] env: - REFLEX_WEB_WINDOWS_OVERRIDE: '1' + REFLEX_WEB_WINDOWS_OVERRIDE: "1" runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -173,7 +176,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/setup_build_env with: - python-version: '3.11.4' + python-version: "3.11.11" run-poetry-install: true create-venv-at-path: .venv - name: Create app directory @@ -192,14 +195,13 @@ jobs: # Check that npm is home npm -v poetry run bash scripts/integration.sh ./rx-shout-from-template prod - reflex-web-macos: if: github.event_name == 'push' && github.ref == 'refs/heads/main' strategy: fail-fast: false matrix: - python-version: ['3.11.5', '3.12.0'] + python-version: ["3.11.11", "3.12.8"] runs-on: macos-latest steps: - uses: actions/checkout@v4 @@ -233,4 +235,3 @@ jobs: --python-version "${{ matrix.python-version }}" --commit-sha "${{ github.sha }}" --pr-id "${{ github.event.pull_request.id }}" --branch-name "${{ github.head_ref || github.ref_name }}" --app-name "reflex-web" --path ./reflex-web/.web - \ No newline at end of file diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 9e6e42a38..4c71e3035 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -6,12 +6,12 @@ concurrency: on: pull_request: - branches: ['main'] + branches: ["main"] push: # Note even though this job is called "pre-commit" and runs "pre-commit", this job will run # also POST-commit on main also! In case there are mishandled merge conflicts / bad auto-resolves # when merging into main branch. - branches: ['main'] + branches: ["main"] jobs: pre-commit: @@ -23,7 +23,7 @@ jobs: with: # running vs. one version of Python is OK # i.e. ruff, black, etc. - python-version: 3.11.5 + python-version: 3.12.8 run-poetry-install: true create-venv-at-path: .venv # TODO pre-commit related stuff can be cached too (not a bottleneck yet) diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 25f5723f3..5477a8188 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -6,13 +6,13 @@ concurrency: on: push: - branches: ['main'] + branches: ["main"] paths-ignore: - - '**/*.md' + - "**/*.md" pull_request: - branches: ['main'] + branches: ["main"] paths-ignore: - - '**/*.md' + - "**/*.md" permissions: contents: read @@ -28,18 +28,22 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest] - python-version: ['3.9.18', '3.10.13', '3.11.5', '3.12.0', '3.13.0'] + python-version: ["3.9.21", "3.10.16", "3.11.11", "3.12.8", "3.13.1"] # Windows is a bit behind on Python version availability in Github exclude: - os: windows-latest - python-version: '3.10.13' + python-version: "3.11.11" - os: windows-latest - python-version: '3.9.18' + python-version: "3.10.16" + - os: windows-latest + python-version: "3.9.21" include: - os: windows-latest - python-version: '3.10.11' + python-version: "3.11.9" - os: windows-latest - python-version: '3.9.13' + python-version: "3.10.11" + - os: windows-latest + python-version: "3.9.13" runs-on: ${{ matrix.os }} # Service containers to run with `runner-job` @@ -89,7 +93,7 @@ jobs: fail-fast: false matrix: # Note: py39, py310 versions chosen due to available arm64 darwin builds. - python-version: ['3.9.13', '3.10.11', '3.11.5', '3.12.0', '3.13.0'] + python-version: ["3.9.21", "3.10.16", "3.11.11", "3.12.8", "3.13.1"] runs-on: macos-latest steps: - uses: actions/checkout@v4 @@ -106,4 +110,4 @@ jobs: run: | export PYTHONUNBUFFERED=1 poetry run uv pip install "pydantic~=1.10" - poetry run pytest tests/units --cov --no-cov-on-fail --cov-report= \ No newline at end of file + poetry run pytest tests/units --cov --no-cov-on-fail --cov-report= diff --git a/reflex/experimental/client_state.py b/reflex/experimental/client_state.py index e37ceb14c..45dfef237 100644 --- a/reflex/experimental/client_state.py +++ b/reflex/experimental/client_state.py @@ -12,7 +12,7 @@ from reflex.event import EventChain, EventHandler, EventSpec, run_script from reflex.utils.imports import ImportVar from reflex.vars import VarData, get_unique_variable_name from reflex.vars.base import LiteralVar, Var -from reflex.vars.function import FunctionVar +from reflex.vars.function import ArgsFunctionOperationBuilder, FunctionVar NoValue = object() @@ -45,6 +45,7 @@ class ClientStateVar(Var): # Track the names of the getters and setters _setter_name: str = dataclasses.field(default="") _getter_name: str = dataclasses.field(default="") + _id_name: str = dataclasses.field(default="") # Whether to add the var and setter to the global `refs` object for use in any Component. _global_ref: bool = dataclasses.field(default=True) @@ -96,6 +97,7 @@ class ClientStateVar(Var): """ if var_name is None: var_name = get_unique_variable_name() + id_name = "id_" + get_unique_variable_name() if not isinstance(var_name, str): raise ValueError("var_name must be a string.") if default is NoValue: @@ -106,19 +108,23 @@ class ClientStateVar(Var): default_var = default setter_name = f"set{var_name.capitalize()}" hooks: dict[str, VarData | None] = { + f"const {id_name} = useId()": None, f"const [{var_name}, {setter_name}] = useState({default_var!s})": None, } imports = { - "react": [ImportVar(tag="useState")], + "react": [ImportVar(tag="useState"), ImportVar(tag="useId")], } if global_ref: - hooks[f"{_client_state_ref(var_name)} = {var_name}"] = None - hooks[f"{_client_state_ref(setter_name)} = {setter_name}"] = None + hooks[f"{_client_state_ref(var_name)} ??= {{}}"] = None + hooks[f"{_client_state_ref(setter_name)} ??= {{}}"] = None + hooks[f"{_client_state_ref(var_name)}[{id_name}] = {var_name}"] = None + hooks[f"{_client_state_ref(setter_name)}[{id_name}] = {setter_name}"] = None imports.update(_refs_import) return cls( _js_expr="", _setter_name=setter_name, _getter_name=var_name, + _id_name=id_name, _global_ref=global_ref, _var_type=default_var._var_type, _var_data=VarData.merge( @@ -144,10 +150,11 @@ class ClientStateVar(Var): return ( Var( _js_expr=( - _client_state_ref(self._getter_name) + _client_state_ref(self._getter_name) + f"[{self._id_name}]" if self._global_ref else self._getter_name - ) + ), + _var_data=self._var_data, ) .to(self._var_type) ._replace( @@ -170,28 +177,43 @@ class ClientStateVar(Var): Returns: A special EventChain Var which will set the value when triggered. """ - setter = ( - _client_state_ref(self._setter_name) - if self._global_ref - else self._setter_name - ) _var_data = VarData(imports=_refs_import if self._global_ref else {}) + + arg_name = get_unique_variable_name() + setter = ( + ArgsFunctionOperationBuilder.create( + args_names=(arg_name,), + return_expr=Var("Array.prototype.forEach.call") + .to(FunctionVar) + .call( + Var("Object.values") + .to(FunctionVar) + .call(Var(_client_state_ref(self._setter_name))), + ArgsFunctionOperationBuilder.create( + args_names=("setter",), + return_expr=Var("setter").to(FunctionVar).call(Var(arg_name)), + ), + ), + _var_data=_var_data, + ) + if self._global_ref + else Var(self._setter_name, _var_data=_var_data).to(FunctionVar) + ) + if value is not NoValue: # This is a hack to make it work like an EventSpec taking an arg value_var = LiteralVar.create(value) - _var_data = VarData.merge(_var_data, value_var._get_all_var_data()) value_str = str(value_var) - if value_str.startswith("_"): + setter = ArgsFunctionOperationBuilder.create( # remove patterns of ["*"] from the value_str using regex - arg = re.sub(r"\[\".*\"\]", "", value_str) - setter = f"(({arg}) => {setter}({value_str}))" - else: - setter = f"(() => {setter}({value_str}))" - return Var( - _js_expr=setter, - _var_data=_var_data, - ).to(FunctionVar, EventChain) + args_names=(re.sub(r"\[\".*\"\]", "", value_str),) + if value_str.startswith("_") + else (), + return_expr=setter.call(value_var), + ) + + return setter.to(FunctionVar, EventChain) @property def set(self) -> Var: From 0ad0a84ee1eae12c2590648aef7997bc93018fe3 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Tue, 7 Jan 2025 15:11:38 -0800 Subject: [PATCH 23/44] fix recursive UI (#4599) * fix recursive UI * get it right pyright * dang it darglint --- reflex/components/base/bare.py | 5 +++- reflex/components/component.py | 39 +++++++++++++++++--------- reflex/components/el/elements/forms.py | 8 ++++-- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/reflex/components/base/bare.py b/reflex/components/base/bare.py index 7cd225deb..e576fac85 100644 --- a/reflex/components/base/bare.py +++ b/reflex/components/base/bare.py @@ -108,11 +108,14 @@ class Bare(Component): return Tagless(contents=f"{{{self.contents!s}}}") return Tagless(contents=str(self.contents)) - def _get_vars(self, include_children: bool = False) -> Iterator[Var]: + def _get_vars( + self, include_children: bool = False, ignore_ids: set[int] | None = None + ) -> Iterator[Var]: """Walk all Vars used in this component. Args: include_children: Whether to include Vars from children. + ignore_ids: The ids to ignore. Yields: The contents if it is a Var, otherwise nothing. diff --git a/reflex/components/component.py b/reflex/components/component.py index 46708e572..8649b593d 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -1020,18 +1020,22 @@ class Component(BaseComponent, ABC): event_args.append(spec) yield event_trigger, event_args - def _get_vars(self, include_children: bool = False) -> list[Var]: + def _get_vars( + self, include_children: bool = False, ignore_ids: set[int] | None = None + ) -> Iterator[Var]: """Walk all Vars used in this component. Args: include_children: Whether to include Vars from children. + ignore_ids: The ids to ignore. - Returns: + Yields: Each var referenced by the component (props, styles, event handlers). """ - vars = getattr(self, "__vars", None) + ignore_ids = ignore_ids or set() + vars: List[Var] | None = getattr(self, "__vars", None) if vars is not None: - return vars + yield from vars vars = self.__vars = [] # Get Vars associated with event trigger arguments. for _, event_vars in self._get_vars_from_event_triggers(self.event_triggers): @@ -1075,12 +1079,15 @@ class Component(BaseComponent, ABC): # Get Vars associated with children. if include_children: for child in self.children: - if not isinstance(child, Component): + if not isinstance(child, Component) or id(child) in ignore_ids: continue - child_vars = child._get_vars(include_children=include_children) + ignore_ids.add(id(child)) + child_vars = child._get_vars( + include_children=include_children, ignore_ids=ignore_ids + ) vars.extend(child_vars) - return vars + yield from vars def _event_trigger_values_use_state(self) -> bool: """Check if the values of a component's event trigger use state. @@ -1811,19 +1818,25 @@ class CustomComponent(Component): for name, prop in self.props.items() ] - def _get_vars(self, include_children: bool = False) -> list[Var]: + def _get_vars( + self, include_children: bool = False, ignore_ids: set[int] | None = None + ) -> Iterator[Var]: """Walk all Vars used in this component. Args: include_children: Whether to include Vars from children. + ignore_ids: The ids to ignore. - Returns: + Yields: Each var referenced by the component (props, styles, event handlers). """ - return ( - super()._get_vars(include_children=include_children) - + [prop for prop in self.props.values() if isinstance(prop, Var)] - + self.get_component(self)._get_vars(include_children=include_children) + ignore_ids = ignore_ids or set() + yield from super()._get_vars( + include_children=include_children, ignore_ids=ignore_ids + ) + yield from filter(lambda prop: isinstance(prop, Var), self.props.values()) + yield from self.get_component(self)._get_vars( + include_children=include_children, ignore_ids=ignore_ids ) @lru_cache(maxsize=None) # noqa diff --git a/reflex/components/el/elements/forms.py b/reflex/components/el/elements/forms.py index 529a5e884..6b2d83c46 100644 --- a/reflex/components/el/elements/forms.py +++ b/reflex/components/el/elements/forms.py @@ -250,8 +250,12 @@ class Form(BaseHTML): ) return form_refs - def _get_vars(self, include_children: bool = True) -> Iterator[Var]: - yield from super()._get_vars(include_children=include_children) + def _get_vars( + self, include_children: bool = True, ignore_ids: set[int] | None = None + ) -> Iterator[Var]: + yield from super()._get_vars( + include_children=include_children, ignore_ids=ignore_ids + ) yield from self._get_form_refs().values() def _exclude_props(self) -> list[str]: From 5d877d54d09a8ae016de1f46f43cc2528ff08a89 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Tue, 7 Jan 2025 15:46:26 -0800 Subject: [PATCH 24/44] Use older python versions for macos actions (#4601) Use python versions that have a darwin/arm64 build for use with the newer (faster) macos actions runners --- .github/workflows/integration_tests.yml | 3 ++- .github/workflows/unit_tests.yml | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index dda9e0211..7e3fecb89 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -201,7 +201,8 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.11.11", "3.12.8"] + # Note: py311 version chosen due to available arm64 darwin builds. + python-version: ["3.11.9", "3.12.8"] runs-on: macos-latest steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/unit_tests.yml b/.github/workflows/unit_tests.yml index 5477a8188..e0a3723ac 100644 --- a/.github/workflows/unit_tests.yml +++ b/.github/workflows/unit_tests.yml @@ -92,8 +92,8 @@ jobs: strategy: fail-fast: false matrix: - # Note: py39, py310 versions chosen due to available arm64 darwin builds. - python-version: ["3.9.21", "3.10.16", "3.11.11", "3.12.8", "3.13.1"] + # Note: py39, py310, py311 versions chosen due to available arm64 darwin builds. + python-version: ["3.9.13", "3.10.11", "3.11.9", "3.12.8", "3.13.1"] runs-on: macos-latest steps: - uses: actions/checkout@v4 From 4c97072a3ca74d8373d90857506b12c5ce1a6975 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Wed, 8 Jan 2025 09:37:33 -0800 Subject: [PATCH 25/44] pyproject.toml: bump to 0.7.0dev1 for further development (#4604) --- pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 3e76ec5b3..fb803fbaf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "reflex" -version = "0.6.8dev1" +version = "0.7.0dev1" description = "Web apps in pure Python." license = "Apache-2.0" authors = [ @@ -108,4 +108,4 @@ asyncio_mode = "auto" [tool.codespell] skip = "docs/*,*.html,examples/*, *.pyi" -ignore-words-list = "te, TreeE" \ No newline at end of file +ignore-words-list = "te, TreeE" From 79611abdabcd4d19f2404e695d65064266701f03 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 8 Jan 2025 17:16:38 -0800 Subject: [PATCH 26/44] make that one line better (#4610) --- reflex/app.py | 5 ++--- reflex/event.py | 2 ++ 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/reflex/app.py b/reflex/app.py index 259fcca29..89ee4c164 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -68,6 +68,7 @@ from reflex.components.core.upload import Upload, get_upload_dir from reflex.components.radix import themes from reflex.config import environment, get_config from reflex.event import ( + _EVENT_FIELDS, BASE_STATE, Event, EventHandler, @@ -1571,9 +1572,7 @@ class EventNamespace(AsyncNamespace): """ fields = data # Get the event. - event = Event( - **{k: v for k, v in fields.items() if k not in ("handler", "event_actions")} - ) + event = Event(**{k: v for k, v in fields.items() if k in _EVENT_FIELDS}) self.token_to_sid[event.token] = sid self.sid_to_token[sid] = event.token diff --git a/reflex/event.py b/reflex/event.py index 8b25c578b..c9058ce19 100644 --- a/reflex/event.py +++ b/reflex/event.py @@ -91,6 +91,8 @@ class Event: return f"{self.token}_{substate}" +_EVENT_FIELDS: set[str] = {f.name for f in dataclasses.fields(Event)} + BACKGROUND_TASK_MARKER = "_reflex_background_task" From fe9c02062d0b9667b1d6fd64ae177f5e1e830a1a Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 9 Jan 2025 16:14:38 -0800 Subject: [PATCH 27/44] EventChain.create accepts arbitrary kwargs (#4609) Pass _var_data when creating LiteralVar Partial fixes for #4608 --- reflex/event.py | 9 +++++++-- reflex/vars/base.py | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/reflex/event.py b/reflex/event.py index c9058ce19..28852fde5 100644 --- a/reflex/event.py +++ b/reflex/event.py @@ -439,6 +439,7 @@ class EventChain(EventActionsMixin): value: EventType, args_spec: ArgsSpec | Sequence[ArgsSpec], key: Optional[str] = None, + **event_chain_kwargs, ) -> Union[EventChain, Var]: """Create an event chain from a variety of input types. @@ -446,6 +447,7 @@ class EventChain(EventActionsMixin): value: The value to create the event chain from. args_spec: The args_spec of the event trigger being bound. key: The key of the event trigger being bound. + **event_chain_kwargs: Additional kwargs to pass to the EventChain constructor. Returns: The event chain. @@ -464,6 +466,7 @@ class EventChain(EventActionsMixin): value=value.guess_type(), args_spec=args_spec, key=key, + **event_chain_kwargs, ) else: raise ValueError( @@ -503,7 +506,9 @@ class EventChain(EventActionsMixin): result = call_event_fn(value, args_spec, key=key) if isinstance(result, Var): # Recursively call this function if the lambda returned an EventChain Var. - return cls.create(value=result, args_spec=args_spec, key=key) + return cls.create( + value=result, args_spec=args_spec, key=key, **event_chain_kwargs + ) events = [*result] # Otherwise, raise an error. @@ -520,7 +525,7 @@ class EventChain(EventActionsMixin): return cls( events=events, args_spec=args_spec, - event_actions={}, + **event_chain_kwargs, ) diff --git a/reflex/vars/base.py b/reflex/vars/base.py index a4496d77d..0a93901cd 100644 --- a/reflex/vars/base.py +++ b/reflex/vars/base.py @@ -581,7 +581,7 @@ class Var(Generic[VAR_TYPE]): # Try to pull the imports and hooks from contained values. if not isinstance(value, str): - return LiteralVar.create(value) + return LiteralVar.create(value, _var_data=_var_data) if _var_is_string is False or _var_is_local is True: return cls( From 427d7c56abe33a300f211680edd22b90c6981ef7 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Thu, 9 Jan 2025 16:30:49 -0800 Subject: [PATCH 28/44] Revert "[ENG-4005] Proxy backend requests on '/' to the frontend (#3300)" (#4614) This reverts commit 438b31f27089c4fb069ec9ab0890bdb2af63bd7e. --- .github/workflows/integration_tests.yml | 2 - .github/workflows/integration_tests_wsl.yml | 1 - poetry.lock | 575 +----------------- pyproject.toml | 5 - reflex/app.py | 6 - reflex/config.py | 5 - reflex/proxy.py | 119 ---- reflex/testing.py | 21 +- reflex/utils/console.py | 7 +- scripts/integration.sh | 2 +- scripts/wait_for_listening_port.py | 52 +- .../init-test/in_docker_test_script.sh | 2 +- 12 files changed, 15 insertions(+), 782 deletions(-) delete mode 100644 reflex/proxy.py diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 7e3fecb89..017336ba5 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -84,8 +84,6 @@ jobs: run: | poetry run reflex export --backend-only - name: Check run --backend-only before init for counter example - env: - WAIT_FOR_LISTENING_PORT_ARGS: --path ping run: | poetry run bash scripts/integration.sh ./reflex-examples/counter dev 8001 --backend-only --backend-port 8001 - name: Init Website for counter example diff --git a/.github/workflows/integration_tests_wsl.yml b/.github/workflows/integration_tests_wsl.yml index f6b3d395b..7a743252b 100644 --- a/.github/workflows/integration_tests_wsl.yml +++ b/.github/workflows/integration_tests_wsl.yml @@ -78,7 +78,6 @@ jobs: shell: wsl-bash {0} run: | export TELEMETRY_ENABLED=false - export WAIT_FOR_LISTENING_PORT_ARGS="--path ping" dos2unix scripts/integration.sh poetry run bash scripts/integration.sh ./reflex-examples/counter dev 8001 --backend-only --backend-port 8001 - name: Init Website for counter example diff --git a/poetry.lock b/poetry.lock index ab65b25b4..cc778d19b 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,142 +1,5 @@ # This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. -[[package]] -name = "aiohappyeyeballs" -version = "2.4.3" -description = "Happy Eyeballs for asyncio" -optional = false -python-versions = ">=3.8" -files = [ - {file = "aiohappyeyeballs-2.4.3-py3-none-any.whl", hash = "sha256:8a7a83727b2756f394ab2895ea0765a0a8c475e3c71e98d43d76f22b4b435572"}, - {file = "aiohappyeyeballs-2.4.3.tar.gz", hash = "sha256:75cf88a15106a5002a8eb1dab212525c00d1f4c0fa96e551c9fbe6f09a621586"}, -] - -[[package]] -name = "aiohttp" -version = "3.10.10" -description = "Async http client/server framework (asyncio)" -optional = false -python-versions = ">=3.8" -files = [ - {file = "aiohttp-3.10.10-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:be7443669ae9c016b71f402e43208e13ddf00912f47f623ee5994e12fc7d4b3f"}, - {file = "aiohttp-3.10.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:7b06b7843929e41a94ea09eb1ce3927865387e3e23ebe108e0d0d09b08d25be9"}, - {file = "aiohttp-3.10.10-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:333cf6cf8e65f6a1e06e9eb3e643a0c515bb850d470902274239fea02033e9a8"}, - {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:274cfa632350225ce3fdeb318c23b4a10ec25c0e2c880eff951a3842cf358ac1"}, - {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d9e5e4a85bdb56d224f412d9c98ae4cbd032cc4f3161818f692cd81766eee65a"}, - {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2b606353da03edcc71130b52388d25f9a30a126e04caef1fd637e31683033abd"}, - {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ab5a5a0c7a7991d90446a198689c0535be89bbd6b410a1f9a66688f0880ec026"}, - {file = "aiohttp-3.10.10-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:578a4b875af3e0daaf1ac6fa983d93e0bbfec3ead753b6d6f33d467100cdc67b"}, - {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:8105fd8a890df77b76dd3054cddf01a879fc13e8af576805d667e0fa0224c35d"}, - {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:3bcd391d083f636c06a68715e69467963d1f9600f85ef556ea82e9ef25f043f7"}, - {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:fbc6264158392bad9df19537e872d476f7c57adf718944cc1e4495cbabf38e2a"}, - {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:e48d5021a84d341bcaf95c8460b152cfbad770d28e5fe14a768988c461b821bc"}, - {file = "aiohttp-3.10.10-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:2609e9ab08474702cc67b7702dbb8a80e392c54613ebe80db7e8dbdb79837c68"}, - {file = "aiohttp-3.10.10-cp310-cp310-win32.whl", hash = "sha256:84afcdea18eda514c25bc68b9af2a2b1adea7c08899175a51fe7c4fb6d551257"}, - {file = "aiohttp-3.10.10-cp310-cp310-win_amd64.whl", hash = "sha256:9c72109213eb9d3874f7ac8c0c5fa90e072d678e117d9061c06e30c85b4cf0e6"}, - {file = "aiohttp-3.10.10-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:c30a0eafc89d28e7f959281b58198a9fa5e99405f716c0289b7892ca345fe45f"}, - {file = "aiohttp-3.10.10-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:258c5dd01afc10015866114e210fb7365f0d02d9d059c3c3415382ab633fcbcb"}, - {file = "aiohttp-3.10.10-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:15ecd889a709b0080f02721255b3f80bb261c2293d3c748151274dfea93ac871"}, - {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f3935f82f6f4a3820270842e90456ebad3af15810cf65932bd24da4463bc0a4c"}, - {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:413251f6fcf552a33c981c4709a6bba37b12710982fec8e558ae944bfb2abd38"}, - {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1720b4f14c78a3089562b8875b53e36b51c97c51adc53325a69b79b4b48ebcb"}, - {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:679abe5d3858b33c2cf74faec299fda60ea9de62916e8b67e625d65bf069a3b7"}, - {file = "aiohttp-3.10.10-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:79019094f87c9fb44f8d769e41dbb664d6e8fcfd62f665ccce36762deaa0e911"}, - {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:fe2fb38c2ed905a2582948e2de560675e9dfbee94c6d5ccdb1301c6d0a5bf092"}, - {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:a3f00003de6eba42d6e94fabb4125600d6e484846dbf90ea8e48a800430cc142"}, - {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:1bbb122c557a16fafc10354b9d99ebf2f2808a660d78202f10ba9d50786384b9"}, - {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:30ca7c3b94708a9d7ae76ff281b2f47d8eaf2579cd05971b5dc681db8caac6e1"}, - {file = "aiohttp-3.10.10-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:df9270660711670e68803107d55c2b5949c2e0f2e4896da176e1ecfc068b974a"}, - {file = "aiohttp-3.10.10-cp311-cp311-win32.whl", hash = "sha256:aafc8ee9b742ce75044ae9a4d3e60e3d918d15a4c2e08a6c3c3e38fa59b92d94"}, - {file = "aiohttp-3.10.10-cp311-cp311-win_amd64.whl", hash = "sha256:362f641f9071e5f3ee6f8e7d37d5ed0d95aae656adf4ef578313ee585b585959"}, - {file = "aiohttp-3.10.10-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:9294bbb581f92770e6ed5c19559e1e99255e4ca604a22c5c6397b2f9dd3ee42c"}, - {file = "aiohttp-3.10.10-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a8fa23fe62c436ccf23ff930149c047f060c7126eae3ccea005f0483f27b2e28"}, - {file = "aiohttp-3.10.10-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:5c6a5b8c7926ba5d8545c7dd22961a107526562da31a7a32fa2456baf040939f"}, - {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:007ec22fbc573e5eb2fb7dec4198ef8f6bf2fe4ce20020798b2eb5d0abda6138"}, - {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9627cc1a10c8c409b5822a92d57a77f383b554463d1884008e051c32ab1b3742"}, - {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:50edbcad60d8f0e3eccc68da67f37268b5144ecc34d59f27a02f9611c1d4eec7"}, - {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a45d85cf20b5e0d0aa5a8dca27cce8eddef3292bc29d72dcad1641f4ed50aa16"}, - {file = "aiohttp-3.10.10-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0b00807e2605f16e1e198f33a53ce3c4523114059b0c09c337209ae55e3823a8"}, - {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:f2d4324a98062be0525d16f768a03e0bbb3b9fe301ceee99611dc9a7953124e6"}, - {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:438cd072f75bb6612f2aca29f8bd7cdf6e35e8f160bc312e49fbecab77c99e3a"}, - {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:baa42524a82f75303f714108fea528ccacf0386af429b69fff141ffef1c534f9"}, - {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a7d8d14fe962153fc681f6366bdec33d4356f98a3e3567782aac1b6e0e40109a"}, - {file = "aiohttp-3.10.10-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:c1277cd707c465cd09572a774559a3cc7c7a28802eb3a2a9472588f062097205"}, - {file = "aiohttp-3.10.10-cp312-cp312-win32.whl", hash = "sha256:59bb3c54aa420521dc4ce3cc2c3fe2ad82adf7b09403fa1f48ae45c0cbde6628"}, - {file = "aiohttp-3.10.10-cp312-cp312-win_amd64.whl", hash = "sha256:0e1b370d8007c4ae31ee6db7f9a2fe801a42b146cec80a86766e7ad5c4a259cf"}, - {file = "aiohttp-3.10.10-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ad7593bb24b2ab09e65e8a1d385606f0f47c65b5a2ae6c551db67d6653e78c28"}, - {file = "aiohttp-3.10.10-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1eb89d3d29adaf533588f209768a9c02e44e4baf832b08118749c5fad191781d"}, - {file = "aiohttp-3.10.10-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:3fe407bf93533a6fa82dece0e74dbcaaf5d684e5a51862887f9eaebe6372cd79"}, - {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50aed5155f819873d23520919e16703fc8925e509abbb1a1491b0087d1cd969e"}, - {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:4f05e9727ce409358baa615dbeb9b969db94324a79b5a5cea45d39bdb01d82e6"}, - {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3dffb610a30d643983aeb185ce134f97f290f8935f0abccdd32c77bed9388b42"}, - {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa6658732517ddabe22c9036479eabce6036655ba87a0224c612e1ae6af2087e"}, - {file = "aiohttp-3.10.10-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:741a46d58677d8c733175d7e5aa618d277cd9d880301a380fd296975a9cdd7bc"}, - {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:e00e3505cd80440f6c98c6d69269dcc2a119f86ad0a9fd70bccc59504bebd68a"}, - {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:ffe595f10566f8276b76dc3a11ae4bb7eba1aac8ddd75811736a15b0d5311414"}, - {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:bdfcf6443637c148c4e1a20c48c566aa694fa5e288d34b20fcdc58507882fed3"}, - {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d183cf9c797a5291e8301790ed6d053480ed94070637bfaad914dd38b0981f67"}, - {file = "aiohttp-3.10.10-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:77abf6665ae54000b98b3c742bc6ea1d1fb31c394bcabf8b5d2c1ac3ebfe7f3b"}, - {file = "aiohttp-3.10.10-cp313-cp313-win32.whl", hash = "sha256:4470c73c12cd9109db8277287d11f9dd98f77fc54155fc71a7738a83ffcc8ea8"}, - {file = "aiohttp-3.10.10-cp313-cp313-win_amd64.whl", hash = "sha256:486f7aabfa292719a2753c016cc3a8f8172965cabb3ea2e7f7436c7f5a22a151"}, - {file = "aiohttp-3.10.10-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:1b66ccafef7336a1e1f0e389901f60c1d920102315a56df85e49552308fc0486"}, - {file = "aiohttp-3.10.10-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:acd48d5b80ee80f9432a165c0ac8cbf9253eaddb6113269a5e18699b33958dbb"}, - {file = "aiohttp-3.10.10-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:3455522392fb15ff549d92fbf4b73b559d5e43dc522588f7eb3e54c3f38beee7"}, - {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45c3b868724137f713a38376fef8120c166d1eadd50da1855c112fe97954aed8"}, - {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:da1dee8948d2137bb51fbb8a53cce6b1bcc86003c6b42565f008438b806cccd8"}, - {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c5ce2ce7c997e1971b7184ee37deb6ea9922ef5163c6ee5aa3c274b05f9e12fa"}, - {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:28529e08fde6f12eba8677f5a8608500ed33c086f974de68cc65ab218713a59d"}, - {file = "aiohttp-3.10.10-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f7db54c7914cc99d901d93a34704833568d86c20925b2762f9fa779f9cd2e70f"}, - {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:03a42ac7895406220124c88911ebee31ba8b2d24c98507f4a8bf826b2937c7f2"}, - {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:7e338c0523d024fad378b376a79faff37fafb3c001872a618cde1d322400a572"}, - {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:038f514fe39e235e9fef6717fbf944057bfa24f9b3db9ee551a7ecf584b5b480"}, - {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:64f6c17757251e2b8d885d728b6433d9d970573586a78b78ba8929b0f41d045a"}, - {file = "aiohttp-3.10.10-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:93429602396f3383a797a2a70e5f1de5df8e35535d7806c9f91df06f297e109b"}, - {file = "aiohttp-3.10.10-cp38-cp38-win32.whl", hash = "sha256:c823bc3971c44ab93e611ab1a46b1eafeae474c0c844aff4b7474287b75fe49c"}, - {file = "aiohttp-3.10.10-cp38-cp38-win_amd64.whl", hash = "sha256:54ca74df1be3c7ca1cf7f4c971c79c2daf48d9aa65dea1a662ae18926f5bc8ce"}, - {file = "aiohttp-3.10.10-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:01948b1d570f83ee7bbf5a60ea2375a89dfb09fd419170e7f5af029510033d24"}, - {file = "aiohttp-3.10.10-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9fc1500fd2a952c5c8e3b29aaf7e3cc6e27e9cfc0a8819b3bce48cc1b849e4cc"}, - {file = "aiohttp-3.10.10-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f614ab0c76397661b90b6851a030004dac502e48260ea10f2441abd2207fbcc7"}, - {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:00819de9e45d42584bed046314c40ea7e9aea95411b38971082cad449392b08c"}, - {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:05646ebe6b94cc93407b3bf34b9eb26c20722384d068eb7339de802154d61bc5"}, - {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:998f3bd3cfc95e9424a6acd7840cbdd39e45bc09ef87533c006f94ac47296090"}, - {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d9010c31cd6fa59438da4e58a7f19e4753f7f264300cd152e7f90d4602449762"}, - {file = "aiohttp-3.10.10-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7ea7ffc6d6d6f8a11e6f40091a1040995cdff02cfc9ba4c2f30a516cb2633554"}, - {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:ef9c33cc5cbca35808f6c74be11eb7f5f6b14d2311be84a15b594bd3e58b5527"}, - {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:ce0cdc074d540265bfeb31336e678b4e37316849d13b308607efa527e981f5c2"}, - {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:597a079284b7ee65ee102bc3a6ea226a37d2b96d0418cc9047490f231dc09fe8"}, - {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:7789050d9e5d0c309c706953e5e8876e38662d57d45f936902e176d19f1c58ab"}, - {file = "aiohttp-3.10.10-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:e7f8b04d83483577fd9200461b057c9f14ced334dcb053090cea1da9c8321a91"}, - {file = "aiohttp-3.10.10-cp39-cp39-win32.whl", hash = "sha256:c02a30b904282777d872266b87b20ed8cc0d1501855e27f831320f471d54d983"}, - {file = "aiohttp-3.10.10-cp39-cp39-win_amd64.whl", hash = "sha256:edfe3341033a6b53a5c522c802deb2079eee5cbfbb0af032a55064bd65c73a23"}, - {file = "aiohttp-3.10.10.tar.gz", hash = "sha256:0631dd7c9f0822cc61c88586ca76d5b5ada26538097d0f1df510b082bad3411a"}, -] - -[package.dependencies] -aiohappyeyeballs = ">=2.3.0" -aiosignal = ">=1.1.2" -async-timeout = {version = ">=4.0,<5.0", markers = "python_version < \"3.11\""} -attrs = ">=17.3.0" -frozenlist = ">=1.1.1" -multidict = ">=4.5,<7.0" -yarl = ">=1.12.0,<2.0" - -[package.extras] -speedups = ["Brotli", "aiodns (>=3.2.0)", "brotlicffi"] - -[[package]] -name = "aiosignal" -version = "1.3.1" -description = "aiosignal: a list of registered asynchronous callbacks" -optional = false -python-versions = ">=3.7" -files = [ - {file = "aiosignal-1.3.1-py3-none-any.whl", hash = "sha256:f8376fb07dd1e86a584e4fcdec80b36b7f81aac666ebc724e2c090300dd83b17"}, - {file = "aiosignal-1.3.1.tar.gz", hash = "sha256:54cd96e15e1649b75d6c87526a6ff0b6c1b0dd3459f43d9ca11d48c339b68cfc"}, -] - -[package.dependencies] -frozenlist = ">=1.1.0" - [[package]] name = "alembic" version = "1.14.0" @@ -189,31 +52,15 @@ doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] trio = ["trio (>=0.26.1)"] -[[package]] -name = "asgiproxy" -version = "0.1.1" -description = "Tools for building HTTP and Websocket proxies for the asynchronous ASGI protocol" -optional = false -python-versions = ">=3.6" -files = [ - {file = "asgiproxy-0.1.1-py3-none-any.whl", hash = "sha256:f5175d43691367c51cc972dda0631096e5f23b3536ca29d859be52de87844734"}, - {file = "asgiproxy-0.1.1.tar.gz", hash = "sha256:9dec4d1d8680277dd52b41813d1123383b8a475b8dc82314e5f6729c6c5fa177"}, -] - -[package.dependencies] -aiohttp = "*" -starlette = "*" -websockets = "*" - [[package]] name = "async-timeout" -version = "4.0.3" +version = "5.0.1" description = "Timeout context manager for asyncio programs" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "async-timeout-4.0.3.tar.gz", hash = "sha256:4640d96be84d82d02ed59ea2b7105a0f7b33abe8703703cd0ab0bf87c427522f"}, - {file = "async_timeout-4.0.3-py3-none-any.whl", hash = "sha256:7405140ff1230c310e51dc27b3145b9092d659ce68ff733fb0cefe3ee42be028"}, + {file = "async_timeout-5.0.1-py3-none-any.whl", hash = "sha256:39e3809566ff85354557ec2398b55e096c8364bacac9405a7a1fa429e77fe76c"}, + {file = "async_timeout-5.0.1.tar.gz", hash = "sha256:d9321a7a3d5a6a5e187e824d2fa0793ce379a202935782d555d6e9d2735677d3"}, ] [[package]] @@ -772,107 +619,6 @@ docs = ["furo (>=2024.8.6)", "sphinx (>=8.0.2)", "sphinx-autodoc-typehints (>=2. testing = ["covdefaults (>=2.3)", "coverage (>=7.6.1)", "diff-cover (>=9.2)", "pytest (>=8.3.3)", "pytest-asyncio (>=0.24)", "pytest-cov (>=5)", "pytest-mock (>=3.14)", "pytest-timeout (>=2.3.1)", "virtualenv (>=20.26.4)"] typing = ["typing-extensions (>=4.12.2)"] -[[package]] -name = "frozenlist" -version = "1.5.0" -description = "A list-like structure which implements collections.abc.MutableSequence" -optional = false -python-versions = ">=3.8" -files = [ - {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:5b6a66c18b5b9dd261ca98dffcb826a525334b2f29e7caa54e182255c5f6a65a"}, - {file = "frozenlist-1.5.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d1b3eb7b05ea246510b43a7e53ed1653e55c2121019a97e60cad7efb881a97bb"}, - {file = "frozenlist-1.5.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:15538c0cbf0e4fa11d1e3a71f823524b0c46299aed6e10ebb4c2089abd8c3bec"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e79225373c317ff1e35f210dd5f1344ff31066ba8067c307ab60254cd3a78ad5"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9272fa73ca71266702c4c3e2d4a28553ea03418e591e377a03b8e3659d94fa76"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:498524025a5b8ba81695761d78c8dd7382ac0b052f34e66939c42df860b8ff17"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:92b5278ed9d50fe610185ecd23c55d8b307d75ca18e94c0e7de328089ac5dcba"}, - {file = "frozenlist-1.5.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f3c8c1dacd037df16e85227bac13cca58c30da836c6f936ba1df0c05d046d8d"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:f2ac49a9bedb996086057b75bf93538240538c6d9b38e57c82d51f75a73409d2"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:e66cc454f97053b79c2ab09c17fbe3c825ea6b4de20baf1be28919460dd7877f"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:5a3ba5f9a0dfed20337d3e966dc359784c9f96503674c2faf015f7fe8e96798c"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:6321899477db90bdeb9299ac3627a6a53c7399c8cd58d25da094007402b039ab"}, - {file = "frozenlist-1.5.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:76e4753701248476e6286f2ef492af900ea67d9706a0155335a40ea21bf3b2f5"}, - {file = "frozenlist-1.5.0-cp310-cp310-win32.whl", hash = "sha256:977701c081c0241d0955c9586ffdd9ce44f7a7795df39b9151cd9a6fd0ce4cfb"}, - {file = "frozenlist-1.5.0-cp310-cp310-win_amd64.whl", hash = "sha256:189f03b53e64144f90990d29a27ec4f7997d91ed3d01b51fa39d2dbe77540fd4"}, - {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:fd74520371c3c4175142d02a976aee0b4cb4a7cc912a60586ffd8d5929979b30"}, - {file = "frozenlist-1.5.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2f3f7a0fbc219fb4455264cae4d9f01ad41ae6ee8524500f381de64ffaa077d5"}, - {file = "frozenlist-1.5.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f47c9c9028f55a04ac254346e92977bf0f166c483c74b4232bee19a6697e4778"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0996c66760924da6e88922756d99b47512a71cfd45215f3570bf1e0b694c206a"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a2fe128eb4edeabe11896cb6af88fca5346059f6c8d807e3b910069f39157869"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1a8ea951bbb6cacd492e3948b8da8c502a3f814f5d20935aae74b5df2b19cf3d"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:de537c11e4aa01d37db0d403b57bd6f0546e71a82347a97c6a9f0dcc532b3a45"}, - {file = "frozenlist-1.5.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c2623347b933fcb9095841f1cc5d4ff0b278addd743e0e966cb3d460278840d"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:cee6798eaf8b1416ef6909b06f7dc04b60755206bddc599f52232606e18179d3"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:f5f9da7f5dbc00a604fe74aa02ae7c98bcede8a3b8b9666f9f86fc13993bc71a"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:90646abbc7a5d5c7c19461d2e3eeb76eb0b204919e6ece342feb6032c9325ae9"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:bdac3c7d9b705d253b2ce370fde941836a5f8b3c5c2b8fd70940a3ea3af7f4f2"}, - {file = "frozenlist-1.5.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:03d33c2ddbc1816237a67f66336616416e2bbb6beb306e5f890f2eb22b959cdf"}, - {file = "frozenlist-1.5.0-cp311-cp311-win32.whl", hash = "sha256:237f6b23ee0f44066219dae14c70ae38a63f0440ce6750f868ee08775073f942"}, - {file = "frozenlist-1.5.0-cp311-cp311-win_amd64.whl", hash = "sha256:0cc974cc93d32c42e7b0f6cf242a6bd941c57c61b618e78b6c0a96cb72788c1d"}, - {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:31115ba75889723431aa9a4e77d5f398f5cf976eea3bdf61749731f62d4a4a21"}, - {file = "frozenlist-1.5.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:7437601c4d89d070eac8323f121fcf25f88674627505334654fd027b091db09d"}, - {file = "frozenlist-1.5.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7948140d9f8ece1745be806f2bfdf390127cf1a763b925c4a805c603df5e697e"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:feeb64bc9bcc6b45c6311c9e9b99406660a9c05ca8a5b30d14a78555088b0b3a"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:683173d371daad49cffb8309779e886e59c2f369430ad28fe715f66d08d4ab1a"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7d57d8f702221405a9d9b40f9da8ac2e4a1a8b5285aac6100f3393675f0a85ee"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:30c72000fbcc35b129cb09956836c7d7abf78ab5416595e4857d1cae8d6251a6"}, - {file = "frozenlist-1.5.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:000a77d6034fbad9b6bb880f7ec073027908f1b40254b5d6f26210d2dab1240e"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:5d7f5a50342475962eb18b740f3beecc685a15b52c91f7d975257e13e029eca9"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:87f724d055eb4785d9be84e9ebf0f24e392ddfad00b3fe036e43f489fafc9039"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:6e9080bb2fb195a046e5177f10d9d82b8a204c0736a97a153c2466127de87784"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:9b93d7aaa36c966fa42efcaf716e6b3900438632a626fb09c049f6a2f09fc631"}, - {file = "frozenlist-1.5.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:52ef692a4bc60a6dd57f507429636c2af8b6046db8b31b18dac02cbc8f507f7f"}, - {file = "frozenlist-1.5.0-cp312-cp312-win32.whl", hash = "sha256:29d94c256679247b33a3dc96cce0f93cbc69c23bf75ff715919332fdbb6a32b8"}, - {file = "frozenlist-1.5.0-cp312-cp312-win_amd64.whl", hash = "sha256:8969190d709e7c48ea386db202d708eb94bdb29207a1f269bab1196ce0dcca1f"}, - {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:7a1a048f9215c90973402e26c01d1cff8a209e1f1b53f72b95c13db61b00f953"}, - {file = "frozenlist-1.5.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:dd47a5181ce5fcb463b5d9e17ecfdb02b678cca31280639255ce9d0e5aa67af0"}, - {file = "frozenlist-1.5.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:1431d60b36d15cda188ea222033eec8e0eab488f39a272461f2e6d9e1a8e63c2"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6482a5851f5d72767fbd0e507e80737f9c8646ae7fd303def99bfe813f76cf7f"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:44c49271a937625619e862baacbd037a7ef86dd1ee215afc298a417ff3270608"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:12f78f98c2f1c2429d42e6a485f433722b0061d5c0b0139efa64f396efb5886b"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ce3aa154c452d2467487765e3adc730a8c153af77ad84096bc19ce19a2400840"}, - {file = "frozenlist-1.5.0-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9b7dc0c4338e6b8b091e8faf0db3168a37101943e687f373dce00959583f7439"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:45e0896250900b5aa25180f9aec243e84e92ac84bd4a74d9ad4138ef3f5c97de"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:561eb1c9579d495fddb6da8959fd2a1fca2c6d060d4113f5844b433fc02f2641"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:df6e2f325bfee1f49f81aaac97d2aa757c7646534a06f8f577ce184afe2f0a9e"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:140228863501b44b809fb39ec56b5d4071f4d0aa6d216c19cbb08b8c5a7eadb9"}, - {file = "frozenlist-1.5.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7707a25d6a77f5d27ea7dc7d1fc608aa0a478193823f88511ef5e6b8a48f9d03"}, - {file = "frozenlist-1.5.0-cp313-cp313-win32.whl", hash = "sha256:31a9ac2b38ab9b5a8933b693db4939764ad3f299fcaa931a3e605bc3460e693c"}, - {file = "frozenlist-1.5.0-cp313-cp313-win_amd64.whl", hash = "sha256:11aabdd62b8b9c4b84081a3c246506d1cddd2dd93ff0ad53ede5defec7886b28"}, - {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:dd94994fc91a6177bfaafd7d9fd951bc8689b0a98168aa26b5f543868548d3ca"}, - {file = "frozenlist-1.5.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2d0da8bbec082bf6bf18345b180958775363588678f64998c2b7609e34719b10"}, - {file = "frozenlist-1.5.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:73f2e31ea8dd7df61a359b731716018c2be196e5bb3b74ddba107f694fbd7604"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:828afae9f17e6de596825cf4228ff28fbdf6065974e5ac1410cecc22f699d2b3"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f1577515d35ed5649d52ab4319db757bb881ce3b2b796d7283e6634d99ace307"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2150cc6305a2c2ab33299453e2968611dacb970d2283a14955923062c8d00b10"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a72b7a6e3cd2725eff67cd64c8f13335ee18fc3c7befc05aed043d24c7b9ccb9"}, - {file = "frozenlist-1.5.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c16d2fa63e0800723139137d667e1056bee1a1cf7965153d2d104b62855e9b99"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:17dcc32fc7bda7ce5875435003220a457bcfa34ab7924a49a1c19f55b6ee185c"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:97160e245ea33d8609cd2b8fd997c850b56db147a304a262abc2b3be021a9171"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:f1e6540b7fa044eee0bb5111ada694cf3dc15f2b0347ca125ee9ca984d5e9e6e"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:91d6c171862df0a6c61479d9724f22efb6109111017c87567cfeb7b5d1449fdf"}, - {file = "frozenlist-1.5.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:c1fac3e2ace2eb1052e9f7c7db480818371134410e1f5c55d65e8f3ac6d1407e"}, - {file = "frozenlist-1.5.0-cp38-cp38-win32.whl", hash = "sha256:b97f7b575ab4a8af9b7bc1d2ef7f29d3afee2226bd03ca3875c16451ad5a7723"}, - {file = "frozenlist-1.5.0-cp38-cp38-win_amd64.whl", hash = "sha256:374ca2dabdccad8e2a76d40b1d037f5bd16824933bf7bcea3e59c891fd4a0923"}, - {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:9bbcdfaf4af7ce002694a4e10a0159d5a8d20056a12b05b45cea944a4953f972"}, - {file = "frozenlist-1.5.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1893f948bf6681733aaccf36c5232c231e3b5166d607c5fa77773611df6dc336"}, - {file = "frozenlist-1.5.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2b5e23253bb709ef57a8e95e6ae48daa9ac5f265637529e4ce6b003a37b2621f"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0f253985bb515ecd89629db13cb58d702035ecd8cfbca7d7a7e29a0e6d39af5f"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:04a5c6babd5e8fb7d3c871dc8b321166b80e41b637c31a995ed844a6139942b6"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a9fe0f1c29ba24ba6ff6abf688cb0b7cf1efab6b6aa6adc55441773c252f7411"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:226d72559fa19babe2ccd920273e767c96a49b9d3d38badd7c91a0fdeda8ea08"}, - {file = "frozenlist-1.5.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:15b731db116ab3aedec558573c1a5eec78822b32292fe4f2f0345b7f697745c2"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:366d8f93e3edfe5a918c874702f78faac300209a4d5bf38352b2c1bdc07a766d"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:1b96af8c582b94d381a1c1f51ffaedeb77c821c690ea5f01da3d70a487dd0a9b"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:c03eff4a41bd4e38415cbed054bbaff4a075b093e2394b6915dca34a40d1e38b"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:50cf5e7ee9b98f22bdecbabf3800ae78ddcc26e4a435515fc72d97903e8488e0"}, - {file = "frozenlist-1.5.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:1e76bfbc72353269c44e0bc2cfe171900fbf7f722ad74c9a7b638052afe6a00c"}, - {file = "frozenlist-1.5.0-cp39-cp39-win32.whl", hash = "sha256:666534d15ba8f0fda3f53969117383d5dc021266b3c1a42c9ec4855e4b58b9d3"}, - {file = "frozenlist-1.5.0-cp39-cp39-win_amd64.whl", hash = "sha256:5c28f4b5dbef8a0d8aad0d4de24d1e9e981728628afaf4ea0792f5d0939372f0"}, - {file = "frozenlist-1.5.0-py3-none-any.whl", hash = "sha256:d994863bba198a4a518b467bb971c56e1db3f180a25c6cf7bb1949c267f748c3"}, - {file = "frozenlist-1.5.0.tar.gz", hash = "sha256:81d5af29e61b9c8348e876d442253723928dce6433e0e76cd925cd83f1b4b817"}, -] - [[package]] name = "greenlet" version = "3.1.1" @@ -1371,110 +1117,6 @@ files = [ {file = "more_itertools-10.5.0-py3-none-any.whl", hash = "sha256:037b0d3203ce90cca8ab1defbbdac29d5f993fc20131f3664dc8d6acfa872aef"}, ] -[[package]] -name = "multidict" -version = "6.1.0" -description = "multidict implementation" -optional = false -python-versions = ">=3.8" -files = [ - {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:3380252550e372e8511d49481bd836264c009adb826b23fefcc5dd3c69692f60"}, - {file = "multidict-6.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:99f826cbf970077383d7de805c0681799491cb939c25450b9b5b3ced03ca99f1"}, - {file = "multidict-6.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a114d03b938376557927ab23f1e950827c3b893ccb94b62fd95d430fd0e5cf53"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b1c416351ee6271b2f49b56ad7f308072f6f44b37118d69c2cad94f3fa8a40d5"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:6b5d83030255983181005e6cfbac1617ce9746b219bc2aad52201ad121226581"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3e97b5e938051226dc025ec80980c285b053ffb1e25a3db2a3aa3bc046bf7f56"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d618649d4e70ac6efcbba75be98b26ef5078faad23592f9b51ca492953012429"}, - {file = "multidict-6.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10524ebd769727ac77ef2278390fb0068d83f3acb7773792a5080f2b0abf7748"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ff3827aef427c89a25cc96ded1759271a93603aba9fb977a6d264648ebf989db"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:06809f4f0f7ab7ea2cabf9caca7d79c22c0758b58a71f9d32943ae13c7ace056"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:f179dee3b863ab1c59580ff60f9d99f632f34ccb38bf67a33ec6b3ecadd0fd76"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:aaed8b0562be4a0876ee3b6946f6869b7bcdb571a5d1496683505944e268b160"}, - {file = "multidict-6.1.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:3c8b88a2ccf5493b6c8da9076fb151ba106960a2df90c2633f342f120751a9e7"}, - {file = "multidict-6.1.0-cp310-cp310-win32.whl", hash = "sha256:4a9cb68166a34117d6646c0023c7b759bf197bee5ad4272f420a0141d7eb03a0"}, - {file = "multidict-6.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:20b9b5fbe0b88d0bdef2012ef7dee867f874b72528cf1d08f1d59b0e3850129d"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:3efe2c2cb5763f2f1b275ad2bf7a287d3f7ebbef35648a9726e3b69284a4f3d6"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:c7053d3b0353a8b9de430a4f4b4268ac9a4fb3481af37dfe49825bf45ca24156"}, - {file = "multidict-6.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27e5fc84ccef8dfaabb09d82b7d179c7cf1a3fbc8a966f8274fcb4ab2eb4cadb"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e2b90b43e696f25c62656389d32236e049568b39320e2735d51f08fd362761b"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d83a047959d38a7ff552ff94be767b7fd79b831ad1cd9920662db05fec24fe72"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d1a9dd711d0877a1ece3d2e4fea11a8e75741ca21954c919406b44e7cf971304"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ec2abea24d98246b94913b76a125e855eb5c434f7c46546046372fe60f666351"}, - {file = "multidict-6.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4867cafcbc6585e4b678876c489b9273b13e9fff9f6d6d66add5e15d11d926cb"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:5b48204e8d955c47c55b72779802b219a39acc3ee3d0116d5080c388970b76e3"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:d8fff389528cad1618fb4b26b95550327495462cd745d879a8c7c2115248e399"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:a7a9541cd308eed5e30318430a9c74d2132e9a8cb46b901326272d780bf2d423"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:da1758c76f50c39a2efd5e9859ce7d776317eb1dd34317c8152ac9251fc574a3"}, - {file = "multidict-6.1.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:c943a53e9186688b45b323602298ab727d8865d8c9ee0b17f8d62d14b56f0753"}, - {file = "multidict-6.1.0-cp311-cp311-win32.whl", hash = "sha256:90f8717cb649eea3504091e640a1b8568faad18bd4b9fcd692853a04475a4b80"}, - {file = "multidict-6.1.0-cp311-cp311-win_amd64.whl", hash = "sha256:82176036e65644a6cc5bd619f65f6f19781e8ec2e5330f51aa9ada7504cc1926"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:b04772ed465fa3cc947db808fa306d79b43e896beb677a56fb2347ca1a49c1fa"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:6180c0ae073bddeb5a97a38c03f30c233e0a4d39cd86166251617d1bbd0af436"}, - {file = "multidict-6.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:071120490b47aa997cca00666923a83f02c7fbb44f71cf7f136df753f7fa8761"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:50b3a2710631848991d0bf7de077502e8994c804bb805aeb2925a981de58ec2e"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b58c621844d55e71c1b7f7c498ce5aa6985d743a1a59034c57a905b3f153c1ef"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:55b6d90641869892caa9ca42ff913f7ff1c5ece06474fbd32fb2cf6834726c95"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4b820514bfc0b98a30e3d85462084779900347e4d49267f747ff54060cc33925"}, - {file = "multidict-6.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:10a9b09aba0c5b48c53761b7c720aaaf7cf236d5fe394cd399c7ba662d5f9966"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:1e16bf3e5fc9f44632affb159d30a437bfe286ce9e02754759be5536b169b305"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:76f364861c3bfc98cbbcbd402d83454ed9e01a5224bb3a28bf70002a230f73e2"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:820c661588bd01a0aa62a1283f20d2be4281b086f80dad9e955e690c75fb54a2"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:0e5f362e895bc5b9e67fe6e4ded2492d8124bdf817827f33c5b46c2fe3ffaca6"}, - {file = "multidict-6.1.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3ec660d19bbc671e3a6443325f07263be452c453ac9e512f5eb935e7d4ac28b3"}, - {file = "multidict-6.1.0-cp312-cp312-win32.whl", hash = "sha256:58130ecf8f7b8112cdb841486404f1282b9c86ccb30d3519faf301b2e5659133"}, - {file = "multidict-6.1.0-cp312-cp312-win_amd64.whl", hash = "sha256:188215fc0aafb8e03341995e7c4797860181562380f81ed0a87ff455b70bf1f1"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:d569388c381b24671589335a3be6e1d45546c2988c2ebe30fdcada8457a31008"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:052e10d2d37810b99cc170b785945421141bf7bb7d2f8799d431e7db229c385f"}, - {file = "multidict-6.1.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f90c822a402cb865e396a504f9fc8173ef34212a342d92e362ca498cad308e28"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b225d95519a5bf73860323e633a664b0d85ad3d5bede6d30d95b35d4dfe8805b"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:23bfd518810af7de1116313ebd9092cb9aa629beb12f6ed631ad53356ed6b86c"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5c09fcfdccdd0b57867577b719c69e347a436b86cd83747f179dbf0cc0d4c1f3"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf6bea52ec97e95560af5ae576bdac3aa3aae0b6758c6efa115236d9e07dae44"}, - {file = "multidict-6.1.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:57feec87371dbb3520da6192213c7d6fc892d5589a93db548331954de8248fd2"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0c3f390dc53279cbc8ba976e5f8035eab997829066756d811616b652b00a23a3"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:59bfeae4b25ec05b34f1956eaa1cb38032282cd4dfabc5056d0a1ec4d696d3aa"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:b2f59caeaf7632cc633b5cf6fc449372b83bbdf0da4ae04d5be36118e46cc0aa"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:37bb93b2178e02b7b618893990941900fd25b6b9ac0fa49931a40aecdf083fe4"}, - {file = "multidict-6.1.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:4e9f48f58c2c523d5a06faea47866cd35b32655c46b443f163d08c6d0ddb17d6"}, - {file = "multidict-6.1.0-cp313-cp313-win32.whl", hash = "sha256:3a37ffb35399029b45c6cc33640a92bef403c9fd388acce75cdc88f58bd19a81"}, - {file = "multidict-6.1.0-cp313-cp313-win_amd64.whl", hash = "sha256:e9aa71e15d9d9beaad2c6b9319edcdc0a49a43ef5c0a4c8265ca9ee7d6c67774"}, - {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:db7457bac39421addd0c8449933ac32d8042aae84a14911a757ae6ca3eef1392"}, - {file = "multidict-6.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d094ddec350a2fb899fec68d8353c78233debde9b7d8b4beeafa70825f1c281a"}, - {file = "multidict-6.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:5845c1fd4866bb5dd3125d89b90e57ed3138241540897de748cdf19de8a2fca2"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9079dfc6a70abe341f521f78405b8949f96db48da98aeb43f9907f342f627cdc"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3914f5aaa0f36d5d60e8ece6a308ee1c9784cd75ec8151062614657a114c4478"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c08be4f460903e5a9d0f76818db3250f12e9c344e79314d1d570fc69d7f4eae4"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d093be959277cb7dee84b801eb1af388b6ad3ca6a6b6bf1ed7585895789d027d"}, - {file = "multidict-6.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3702ea6872c5a2a4eeefa6ffd36b042e9773f05b1f37ae3ef7264b1163c2dcf6"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:2090f6a85cafc5b2db085124d752757c9d251548cedabe9bd31afe6363e0aff2"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:f67f217af4b1ff66c68a87318012de788dd95fcfeb24cc889011f4e1c7454dfd"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:189f652a87e876098bbc67b4da1049afb5f5dfbaa310dd67c594b01c10388db6"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:6bb5992037f7a9eff7991ebe4273ea7f51f1c1c511e6a2ce511d0e7bdb754492"}, - {file = "multidict-6.1.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:ac10f4c2b9e770c4e393876e35a7046879d195cd123b4f116d299d442b335bcd"}, - {file = "multidict-6.1.0-cp38-cp38-win32.whl", hash = "sha256:e27bbb6d14416713a8bd7aaa1313c0fc8d44ee48d74497a0ff4c3a1b6ccb5167"}, - {file = "multidict-6.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:22f3105d4fb15c8f57ff3959a58fcab6ce36814486500cd7485651230ad4d4ef"}, - {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:4e18b656c5e844539d506a0a06432274d7bd52a7487e6828c63a63d69185626c"}, - {file = "multidict-6.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:a185f876e69897a6f3325c3f19f26a297fa058c5e456bfcff8015e9a27e83ae1"}, - {file = "multidict-6.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab7c4ceb38d91570a650dba194e1ca87c2b543488fe9309b4212694174fd539c"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e617fb6b0b6953fffd762669610c1c4ffd05632c138d61ac7e14ad187870669c"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:16e5f4bf4e603eb1fdd5d8180f1a25f30056f22e55ce51fb3d6ad4ab29f7d96f"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f4c035da3f544b1882bac24115f3e2e8760f10a0107614fc9839fd232200b875"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:957cf8e4b6e123a9eea554fa7ebc85674674b713551de587eb318a2df3e00255"}, - {file = "multidict-6.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:483a6aea59cb89904e1ceabd2b47368b5600fb7de78a6e4a2c2987b2d256cf30"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:87701f25a2352e5bf7454caa64757642734da9f6b11384c1f9d1a8e699758057"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:682b987361e5fd7a139ed565e30d81fd81e9629acc7d925a205366877d8c8657"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:ce2186a7df133a9c895dea3331ddc5ddad42cdd0d1ea2f0a51e5d161e4762f28"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:9f636b730f7e8cb19feb87094949ba54ee5357440b9658b2a32a5ce4bce53972"}, - {file = "multidict-6.1.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:73eae06aa53af2ea5270cc066dcaf02cc60d2994bbb2c4ef5764949257d10f43"}, - {file = "multidict-6.1.0-cp39-cp39-win32.whl", hash = "sha256:1ca0083e80e791cffc6efce7660ad24af66c8d4079d2a750b29001b53ff59ada"}, - {file = "multidict-6.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:aa466da5b15ccea564bdab9c89175c762bc12825f4659c11227f515cee76fa4a"}, - {file = "multidict-6.1.0-py3-none-any.whl", hash = "sha256:48e171e52d1c4d33888e529b999e5900356b9ae588c2f09a52dcefb158b27506"}, - {file = "multidict-6.1.0.tar.gz", hash = "sha256:22ae2ebf9b0c69d206c003e2f6a914ea33f0a932d4aa16f236afc049d9958f4a"}, -] - -[package.dependencies] -typing-extensions = {version = ">=4.1.0", markers = "python_version < \"3.11\""} - [[package]] name = "nh3" version = "0.2.19" @@ -1505,7 +1147,6 @@ files = [ {file = "nh3-0.2.19-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:00810cd5275f5c3f44b9eb0e521d1a841ee2f8023622de39ffc7d88bd533d8e0"}, {file = "nh3-0.2.19-cp38-abi3-win32.whl", hash = "sha256:7e98621856b0a911c21faa5eef8f8ea3e691526c2433f9afc2be713cb6fbdb48"}, {file = "nh3-0.2.19-cp38-abi3-win_amd64.whl", hash = "sha256:75c7cafb840f24430b009f7368945cb5ca88b2b54bb384ebfba495f16bc9c121"}, - {file = "nh3-0.2.19.tar.gz", hash = "sha256:790056b54c068ff8dceb443eaefb696b84beff58cca6c07afd754d17692a4804"}, ] [[package]] @@ -1967,113 +1608,6 @@ nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" -[[package]] -name = "propcache" -version = "0.2.0" -description = "Accelerated property cache" -optional = false -python-versions = ">=3.8" -files = [ - {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:c5869b8fd70b81835a6f187c5fdbe67917a04d7e52b6e7cc4e5fe39d55c39d58"}, - {file = "propcache-0.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:952e0d9d07609d9c5be361f33b0d6d650cd2bae393aabb11d9b719364521984b"}, - {file = "propcache-0.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:33ac8f098df0585c0b53009f039dfd913b38c1d2edafed0cedcc0c32a05aa110"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:97e48e8875e6c13909c800fa344cd54cc4b2b0db1d5f911f840458a500fde2c2"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:388f3217649d6d59292b722d940d4d2e1e6a7003259eb835724092a1cca0203a"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f571aea50ba5623c308aa146eb650eebf7dbe0fd8c5d946e28343cb3b5aad577"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3dfafb44f7bb35c0c06eda6b2ab4bfd58f02729e7c4045e179f9a861b07c9850"}, - {file = "propcache-0.2.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a3ebe9a75be7ab0b7da2464a77bb27febcb4fab46a34f9288f39d74833db7f61"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:d2f0d0f976985f85dfb5f3d685697ef769faa6b71993b46b295cdbbd6be8cc37"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:a3dc1a4b165283bd865e8f8cb5f0c64c05001e0718ed06250d8cac9bec115b48"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:9e0f07b42d2a50c7dd2d8675d50f7343d998c64008f1da5fef888396b7f84630"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:e63e3e1e0271f374ed489ff5ee73d4b6e7c60710e1f76af5f0e1a6117cd26394"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:56bb5c98f058a41bb58eead194b4db8c05b088c93d94d5161728515bd52b052b"}, - {file = "propcache-0.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7665f04d0c7f26ff8bb534e1c65068409bf4687aa2534faf7104d7182debb336"}, - {file = "propcache-0.2.0-cp310-cp310-win32.whl", hash = "sha256:7cf18abf9764746b9c8704774d8b06714bcb0a63641518a3a89c7f85cc02c2ad"}, - {file = "propcache-0.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:cfac69017ef97db2438efb854edf24f5a29fd09a536ff3a992b75990720cdc99"}, - {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:63f13bf09cc3336eb04a837490b8f332e0db41da66995c9fd1ba04552e516354"}, - {file = "propcache-0.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:608cce1da6f2672a56b24a015b42db4ac612ee709f3d29f27a00c943d9e851de"}, - {file = "propcache-0.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:466c219deee4536fbc83c08d09115249db301550625c7fef1c5563a584c9bc87"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fc2db02409338bf36590aa985a461b2c96fce91f8e7e0f14c50c5fcc4f229016"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a6ed8db0a556343d566a5c124ee483ae113acc9a557a807d439bcecc44e7dfbb"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:91997d9cb4a325b60d4e3f20967f8eb08dfcb32b22554d5ef78e6fd1dda743a2"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4c7dde9e533c0a49d802b4f3f218fa9ad0a1ce21f2c2eb80d5216565202acab4"}, - {file = "propcache-0.2.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffcad6c564fe6b9b8916c1aefbb37a362deebf9394bd2974e9d84232e3e08504"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:97a58a28bcf63284e8b4d7b460cbee1edaab24634e82059c7b8c09e65284f178"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:945db8ee295d3af9dbdbb698cce9bbc5c59b5c3fe328bbc4387f59a8a35f998d"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:39e104da444a34830751715f45ef9fc537475ba21b7f1f5b0f4d71a3b60d7fe2"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:c5ecca8f9bab618340c8e848d340baf68bcd8ad90a8ecd7a4524a81c1764b3db"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:c436130cc779806bdf5d5fae0d848713105472b8566b75ff70048c47d3961c5b"}, - {file = "propcache-0.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:191db28dc6dcd29d1a3e063c3be0b40688ed76434622c53a284e5427565bbd9b"}, - {file = "propcache-0.2.0-cp311-cp311-win32.whl", hash = "sha256:5f2564ec89058ee7c7989a7b719115bdfe2a2fb8e7a4543b8d1c0cc4cf6478c1"}, - {file = "propcache-0.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:6e2e54267980349b723cff366d1e29b138b9a60fa376664a157a342689553f71"}, - {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:2ee7606193fb267be4b2e3b32714f2d58cad27217638db98a60f9efb5efeccc2"}, - {file = "propcache-0.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:91ee8fc02ca52e24bcb77b234f22afc03288e1dafbb1f88fe24db308910c4ac7"}, - {file = "propcache-0.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2e900bad2a8456d00a113cad8c13343f3b1f327534e3589acc2219729237a2e8"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f52a68c21363c45297aca15561812d542f8fc683c85201df0bebe209e349f793"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1e41d67757ff4fbc8ef2af99b338bfb955010444b92929e9e55a6d4dcc3c4f09"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a64e32f8bd94c105cc27f42d3b658902b5bcc947ece3c8fe7bc1b05982f60e89"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:55346705687dbd7ef0d77883ab4f6fabc48232f587925bdaf95219bae072491e"}, - {file = "propcache-0.2.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00181262b17e517df2cd85656fcd6b4e70946fe62cd625b9d74ac9977b64d8d9"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:6994984550eaf25dd7fc7bd1b700ff45c894149341725bb4edc67f0ffa94efa4"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:56295eb1e5f3aecd516d91b00cfd8bf3a13991de5a479df9e27dd569ea23959c"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:439e76255daa0f8151d3cb325f6dd4a3e93043e6403e6491813bcaaaa8733887"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:f6475a1b2ecb310c98c28d271a30df74f9dd436ee46d09236a6b750a7599ce57"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:3444cdba6628accf384e349014084b1cacd866fbb88433cd9d279d90a54e0b23"}, - {file = "propcache-0.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:4a9d9b4d0a9b38d1c391bb4ad24aa65f306c6f01b512e10a8a34a2dc5675d348"}, - {file = "propcache-0.2.0-cp312-cp312-win32.whl", hash = "sha256:69d3a98eebae99a420d4b28756c8ce6ea5a29291baf2dc9ff9414b42676f61d5"}, - {file = "propcache-0.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:ad9c9b99b05f163109466638bd30ada1722abb01bbb85c739c50b6dc11f92dc3"}, - {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:ecddc221a077a8132cf7c747d5352a15ed763b674c0448d811f408bf803d9ad7"}, - {file = "propcache-0.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:0e53cb83fdd61cbd67202735e6a6687a7b491c8742dfc39c9e01e80354956763"}, - {file = "propcache-0.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:92fe151145a990c22cbccf9ae15cae8ae9eddabfc949a219c9f667877e40853d"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d6a21ef516d36909931a2967621eecb256018aeb11fc48656e3257e73e2e247a"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f88a4095e913f98988f5b338c1d4d5d07dbb0b6bad19892fd447484e483ba6b"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5a5b3bb545ead161be780ee85a2b54fdf7092815995661947812dde94a40f6fb"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67aeb72e0f482709991aa91345a831d0b707d16b0257e8ef88a2ad246a7280bf"}, - {file = "propcache-0.2.0-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3c997f8c44ec9b9b0bcbf2d422cc00a1d9b9c681f56efa6ca149a941e5560da2"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:2a66df3d4992bc1d725b9aa803e8c5a66c010c65c741ad901e260ece77f58d2f"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:3ebbcf2a07621f29638799828b8d8668c421bfb94c6cb04269130d8de4fb7136"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1235c01ddaa80da8235741e80815ce381c5267f96cc49b1477fdcf8c047ef325"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:3947483a381259c06921612550867b37d22e1df6d6d7e8361264b6d037595f44"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:d5bed7f9805cc29c780f3aee05de3262ee7ce1f47083cfe9f77471e9d6777e83"}, - {file = "propcache-0.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4a91d44379f45f5e540971d41e4626dacd7f01004826a18cb048e7da7e96544"}, - {file = "propcache-0.2.0-cp313-cp313-win32.whl", hash = "sha256:f902804113e032e2cdf8c71015651c97af6418363bea8d78dc0911d56c335032"}, - {file = "propcache-0.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:8f188cfcc64fb1266f4684206c9de0e80f54622c3f22a910cbd200478aeae61e"}, - {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:53d1bd3f979ed529f0805dd35ddaca330f80a9a6d90bc0121d2ff398f8ed8861"}, - {file = "propcache-0.2.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:83928404adf8fb3d26793665633ea79b7361efa0287dfbd372a7e74311d51ee6"}, - {file = "propcache-0.2.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:77a86c261679ea5f3896ec060be9dc8e365788248cc1e049632a1be682442063"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:218db2a3c297a3768c11a34812e63b3ac1c3234c3a086def9c0fee50d35add1f"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:7735e82e3498c27bcb2d17cb65d62c14f1100b71723b68362872bca7d0913d90"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:20a617c776f520c3875cf4511e0d1db847a076d720714ae35ffe0df3e440be68"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:67b69535c870670c9f9b14a75d28baa32221d06f6b6fa6f77a0a13c5a7b0a5b9"}, - {file = "propcache-0.2.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4569158070180c3855e9c0791c56be3ceeb192defa2cdf6a3f39e54319e56b89"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_aarch64.whl", hash = "sha256:db47514ffdbd91ccdc7e6f8407aac4ee94cc871b15b577c1c324236b013ddd04"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_armv7l.whl", hash = "sha256:2a60ad3e2553a74168d275a0ef35e8c0a965448ffbc3b300ab3a5bb9956c2162"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_i686.whl", hash = "sha256:662dd62358bdeaca0aee5761de8727cfd6861432e3bb828dc2a693aa0471a563"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_ppc64le.whl", hash = "sha256:25a1f88b471b3bc911d18b935ecb7115dff3a192b6fef46f0bfaf71ff4f12418"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_s390x.whl", hash = "sha256:f60f0ac7005b9f5a6091009b09a419ace1610e163fa5deaba5ce3484341840e7"}, - {file = "propcache-0.2.0-cp38-cp38-musllinux_1_2_x86_64.whl", hash = "sha256:74acd6e291f885678631b7ebc85d2d4aec458dd849b8c841b57ef04047833bed"}, - {file = "propcache-0.2.0-cp38-cp38-win32.whl", hash = "sha256:d9b6ddac6408194e934002a69bcaadbc88c10b5f38fb9307779d1c629181815d"}, - {file = "propcache-0.2.0-cp38-cp38-win_amd64.whl", hash = "sha256:676135dcf3262c9c5081cc8f19ad55c8a64e3f7282a21266d05544450bffc3a5"}, - {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:25c8d773a62ce0451b020c7b29a35cfbc05de8b291163a7a0f3b7904f27253e6"}, - {file = "propcache-0.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:375a12d7556d462dc64d70475a9ee5982465fbb3d2b364f16b86ba9135793638"}, - {file = "propcache-0.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1ec43d76b9677637a89d6ab86e1fef70d739217fefa208c65352ecf0282be957"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f45eec587dafd4b2d41ac189c2156461ebd0c1082d2fe7013571598abb8505d1"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:bc092ba439d91df90aea38168e11f75c655880c12782facf5cf9c00f3d42b562"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:fa1076244f54bb76e65e22cb6910365779d5c3d71d1f18b275f1dfc7b0d71b4d"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:682a7c79a2fbf40f5dbb1eb6bfe2cd865376deeac65acf9beb607505dced9e12"}, - {file = "propcache-0.2.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:8e40876731f99b6f3c897b66b803c9e1c07a989b366c6b5b475fafd1f7ba3fb8"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:363ea8cd3c5cb6679f1c2f5f1f9669587361c062e4899fce56758efa928728f8"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:140fbf08ab3588b3468932974a9331aff43c0ab8a2ec2c608b6d7d1756dbb6cb"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:e70fac33e8b4ac63dfc4c956fd7d85a0b1139adcfc0d964ce288b7c527537fea"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:b33d7a286c0dc1a15f5fc864cc48ae92a846df287ceac2dd499926c3801054a6"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:f6d5749fdd33d90e34c2efb174c7e236829147a2713334d708746e94c4bde40d"}, - {file = "propcache-0.2.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:22aa8f2272d81d9317ff5756bb108021a056805ce63dd3630e27d042c8092798"}, - {file = "propcache-0.2.0-cp39-cp39-win32.whl", hash = "sha256:73e4b40ea0eda421b115248d7e79b59214411109a5bc47d0d48e4c73e3b8fcf9"}, - {file = "propcache-0.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:9517d5e9e0731957468c29dbfd0f976736a0e55afaea843726e887f36fe017df"}, - {file = "propcache-0.2.0-py3-none-any.whl", hash = "sha256:2ccc28197af5313706511fab3a8b66dcd6da067a1331372c82ea1cb74285e036"}, - {file = "propcache-0.2.0.tar.gz", hash = "sha256:df81779732feb9d01e5d513fad0122efb3d53bbc75f61b2a4f29a020bc985e70"}, -] - [[package]] name = "psutil" version = "6.1.0" @@ -3520,102 +3054,6 @@ files = [ [package.dependencies] h11 = ">=0.9.0,<1" -[[package]] -name = "yarl" -version = "1.17.1" -description = "Yet another URL library" -optional = false -python-versions = ">=3.9" -files = [ - {file = "yarl-1.17.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:0b1794853124e2f663f0ea54efb0340b457f08d40a1cef78edfa086576179c91"}, - {file = "yarl-1.17.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:fbea1751729afe607d84acfd01efd95e3b31db148a181a441984ce9b3d3469da"}, - {file = "yarl-1.17.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8ee427208c675f1b6e344a1f89376a9613fc30b52646a04ac0c1f6587c7e46ec"}, - {file = "yarl-1.17.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b74ff4767d3ef47ffe0cd1d89379dc4d828d4873e5528976ced3b44fe5b0a21"}, - {file = "yarl-1.17.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:62a91aefff3d11bf60e5956d340eb507a983a7ec802b19072bb989ce120cd948"}, - {file = "yarl-1.17.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:846dd2e1243407133d3195d2d7e4ceefcaa5f5bf7278f0a9bda00967e6326b04"}, - {file = "yarl-1.17.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e844be8d536afa129366d9af76ed7cb8dfefec99f5f1c9e4f8ae542279a6dc3"}, - {file = "yarl-1.17.1-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc7c92c1baa629cb03ecb0c3d12564f172218fb1739f54bf5f3881844daadc6d"}, - {file = "yarl-1.17.1-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:ae3476e934b9d714aa8000d2e4c01eb2590eee10b9d8cd03e7983ad65dfbfcba"}, - {file = "yarl-1.17.1-cp310-cp310-musllinux_1_2_armv7l.whl", hash = "sha256:c7e177c619342e407415d4f35dec63d2d134d951e24b5166afcdfd1362828e17"}, - {file = "yarl-1.17.1-cp310-cp310-musllinux_1_2_i686.whl", hash = "sha256:64cc6e97f14cf8a275d79c5002281f3040c12e2e4220623b5759ea7f9868d6a5"}, - {file = "yarl-1.17.1-cp310-cp310-musllinux_1_2_ppc64le.whl", hash = "sha256:84c063af19ef5130084db70ada40ce63a84f6c1ef4d3dbc34e5e8c4febb20822"}, - {file = "yarl-1.17.1-cp310-cp310-musllinux_1_2_s390x.whl", hash = "sha256:482c122b72e3c5ec98f11457aeb436ae4aecca75de19b3d1de7cf88bc40db82f"}, - {file = "yarl-1.17.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:380e6c38ef692b8fd5a0f6d1fa8774d81ebc08cfbd624b1bca62a4d4af2f9931"}, - {file = "yarl-1.17.1-cp310-cp310-win32.whl", hash = "sha256:16bca6678a83657dd48df84b51bd56a6c6bd401853aef6d09dc2506a78484c7b"}, - {file = "yarl-1.17.1-cp310-cp310-win_amd64.whl", hash = "sha256:561c87fea99545ef7d692403c110b2f99dced6dff93056d6e04384ad3bc46243"}, - {file = "yarl-1.17.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:cbad927ea8ed814622305d842c93412cb47bd39a496ed0f96bfd42b922b4a217"}, - {file = "yarl-1.17.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:fca4b4307ebe9c3ec77a084da3a9d1999d164693d16492ca2b64594340999988"}, - {file = "yarl-1.17.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ff5c6771c7e3511a06555afa317879b7db8d640137ba55d6ab0d0c50425cab75"}, - {file = "yarl-1.17.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5b29beab10211a746f9846baa39275e80034e065460d99eb51e45c9a9495bcca"}, - {file = "yarl-1.17.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1a52a1ffdd824fb1835272e125385c32fd8b17fbdefeedcb4d543cc23b332d74"}, - {file = "yarl-1.17.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:58c8e9620eb82a189c6c40cb6b59b4e35b2ee68b1f2afa6597732a2b467d7e8f"}, - {file = "yarl-1.17.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d216e5d9b8749563c7f2c6f7a0831057ec844c68b4c11cb10fc62d4fd373c26d"}, - {file = "yarl-1.17.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:881764d610e3269964fc4bb3c19bb6fce55422828e152b885609ec176b41cf11"}, - {file = "yarl-1.17.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:8c79e9d7e3d8a32d4824250a9c6401194fb4c2ad9a0cec8f6a96e09a582c2cc0"}, - {file = "yarl-1.17.1-cp311-cp311-musllinux_1_2_armv7l.whl", hash = "sha256:299f11b44d8d3a588234adbe01112126010bd96d9139c3ba7b3badd9829261c3"}, - {file = "yarl-1.17.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:cc7d768260f4ba4ea01741c1b5fe3d3a6c70eb91c87f4c8761bbcce5181beafe"}, - {file = "yarl-1.17.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:de599af166970d6a61accde358ec9ded821234cbbc8c6413acfec06056b8e860"}, - {file = "yarl-1.17.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:2b24ec55fad43e476905eceaf14f41f6478780b870eda5d08b4d6de9a60b65b4"}, - {file = "yarl-1.17.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:9fb815155aac6bfa8d86184079652c9715c812d506b22cfa369196ef4e99d1b4"}, - {file = "yarl-1.17.1-cp311-cp311-win32.whl", hash = "sha256:7615058aabad54416ddac99ade09a5510cf77039a3b903e94e8922f25ed203d7"}, - {file = "yarl-1.17.1-cp311-cp311-win_amd64.whl", hash = "sha256:14bc88baa44e1f84164a392827b5defb4fa8e56b93fecac3d15315e7c8e5d8b3"}, - {file = "yarl-1.17.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:327828786da2006085a4d1feb2594de6f6d26f8af48b81eb1ae950c788d97f61"}, - {file = "yarl-1.17.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cc353841428d56b683a123a813e6a686e07026d6b1c5757970a877195f880c2d"}, - {file = "yarl-1.17.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:c73df5b6e8fabe2ddb74876fb82d9dd44cbace0ca12e8861ce9155ad3c886139"}, - {file = "yarl-1.17.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0bdff5e0995522706c53078f531fb586f56de9c4c81c243865dd5c66c132c3b5"}, - {file = "yarl-1.17.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:06157fb3c58f2736a5e47c8fcbe1afc8b5de6fb28b14d25574af9e62150fcaac"}, - {file = "yarl-1.17.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1654ec814b18be1af2c857aa9000de7a601400bd4c9ca24629b18486c2e35463"}, - {file = "yarl-1.17.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f6595c852ca544aaeeb32d357e62c9c780eac69dcd34e40cae7b55bc4fb1147"}, - {file = "yarl-1.17.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:459e81c2fb920b5f5df744262d1498ec2c8081acdcfe18181da44c50f51312f7"}, - {file = "yarl-1.17.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:7e48cdb8226644e2fbd0bdb0a0f87906a3db07087f4de77a1b1b1ccfd9e93685"}, - {file = "yarl-1.17.1-cp312-cp312-musllinux_1_2_armv7l.whl", hash = "sha256:d9b6b28a57feb51605d6ae5e61a9044a31742db557a3b851a74c13bc61de5172"}, - {file = "yarl-1.17.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:e594b22688d5747b06e957f1ef822060cb5cb35b493066e33ceac0cf882188b7"}, - {file = "yarl-1.17.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:5f236cb5999ccd23a0ab1bd219cfe0ee3e1c1b65aaf6dd3320e972f7ec3a39da"}, - {file = "yarl-1.17.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:a2a64e62c7a0edd07c1c917b0586655f3362d2c2d37d474db1a509efb96fea1c"}, - {file = "yarl-1.17.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:d0eea830b591dbc68e030c86a9569826145df485b2b4554874b07fea1275a199"}, - {file = "yarl-1.17.1-cp312-cp312-win32.whl", hash = "sha256:46ddf6e0b975cd680eb83318aa1d321cb2bf8d288d50f1754526230fcf59ba96"}, - {file = "yarl-1.17.1-cp312-cp312-win_amd64.whl", hash = "sha256:117ed8b3732528a1e41af3aa6d4e08483c2f0f2e3d3d7dca7cf538b3516d93df"}, - {file = "yarl-1.17.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:5d1d42556b063d579cae59e37a38c61f4402b47d70c29f0ef15cee1acaa64488"}, - {file = "yarl-1.17.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:c0167540094838ee9093ef6cc2c69d0074bbf84a432b4995835e8e5a0d984374"}, - {file = "yarl-1.17.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:2f0a6423295a0d282d00e8701fe763eeefba8037e984ad5de44aa349002562ac"}, - {file = "yarl-1.17.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e5b078134f48552c4d9527db2f7da0b5359abd49393cdf9794017baec7506170"}, - {file = "yarl-1.17.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d401f07261dc5aa36c2e4efc308548f6ae943bfff20fcadb0a07517a26b196d8"}, - {file = "yarl-1.17.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5f1ac7359e17efe0b6e5fec21de34145caef22b260e978336f325d5c84e6938"}, - {file = "yarl-1.17.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7f63d176a81555984e91f2c84c2a574a61cab7111cc907e176f0f01538e9ff6e"}, - {file = "yarl-1.17.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9e275792097c9f7e80741c36de3b61917aebecc08a67ae62899b074566ff8556"}, - {file = "yarl-1.17.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:81713b70bea5c1386dc2f32a8f0dab4148a2928c7495c808c541ee0aae614d67"}, - {file = "yarl-1.17.1-cp313-cp313-musllinux_1_2_armv7l.whl", hash = "sha256:aa46dce75078fceaf7cecac5817422febb4355fbdda440db55206e3bd288cfb8"}, - {file = "yarl-1.17.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:1ce36ded585f45b1e9bb36d0ae94765c6608b43bd2e7f5f88079f7a85c61a4d3"}, - {file = "yarl-1.17.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:2d374d70fdc36f5863b84e54775452f68639bc862918602d028f89310a034ab0"}, - {file = "yarl-1.17.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:2d9f0606baaec5dd54cb99667fcf85183a7477f3766fbddbe3f385e7fc253299"}, - {file = "yarl-1.17.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:b0341e6d9a0c0e3cdc65857ef518bb05b410dbd70d749a0d33ac0f39e81a4258"}, - {file = "yarl-1.17.1-cp313-cp313-win32.whl", hash = "sha256:2e7ba4c9377e48fb7b20dedbd473cbcbc13e72e1826917c185157a137dac9df2"}, - {file = "yarl-1.17.1-cp313-cp313-win_amd64.whl", hash = "sha256:949681f68e0e3c25377462be4b658500e85ca24323d9619fdc41f68d46a1ffda"}, - {file = "yarl-1.17.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8994b29c462de9a8fce2d591028b986dbbe1b32f3ad600b2d3e1c482c93abad6"}, - {file = "yarl-1.17.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:f9cbfbc5faca235fbdf531b93aa0f9f005ec7d267d9d738761a4d42b744ea159"}, - {file = "yarl-1.17.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b40d1bf6e6f74f7c0a567a9e5e778bbd4699d1d3d2c0fe46f4b717eef9e96b95"}, - {file = "yarl-1.17.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f5efe0661b9fcd6246f27957f6ae1c0eb29bc60552820f01e970b4996e016004"}, - {file = "yarl-1.17.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b5c4804e4039f487e942c13381e6c27b4b4e66066d94ef1fae3f6ba8b953f383"}, - {file = "yarl-1.17.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b5d6a6c9602fd4598fa07e0389e19fe199ae96449008d8304bf5d47cb745462e"}, - {file = "yarl-1.17.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4c9156c4d1eb490fe374fb294deeb7bc7eaccda50e23775b2354b6a6739934"}, - {file = "yarl-1.17.1-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d6324274b4e0e2fa1b3eccb25997b1c9ed134ff61d296448ab8269f5ac068c4c"}, - {file = "yarl-1.17.1-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:d8a8b74d843c2638f3864a17d97a4acda58e40d3e44b6303b8cc3d3c44ae2d29"}, - {file = "yarl-1.17.1-cp39-cp39-musllinux_1_2_armv7l.whl", hash = "sha256:7fac95714b09da9278a0b52e492466f773cfe37651cf467a83a1b659be24bf71"}, - {file = "yarl-1.17.1-cp39-cp39-musllinux_1_2_i686.whl", hash = "sha256:c180ac742a083e109c1a18151f4dd8675f32679985a1c750d2ff806796165b55"}, - {file = "yarl-1.17.1-cp39-cp39-musllinux_1_2_ppc64le.whl", hash = "sha256:578d00c9b7fccfa1745a44f4eddfdc99d723d157dad26764538fbdda37209857"}, - {file = "yarl-1.17.1-cp39-cp39-musllinux_1_2_s390x.whl", hash = "sha256:1a3b91c44efa29e6c8ef8a9a2b583347998e2ba52c5d8280dbd5919c02dfc3b5"}, - {file = "yarl-1.17.1-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:a7ac5b4984c468ce4f4a553df281450df0a34aefae02e58d77a0847be8d1e11f"}, - {file = "yarl-1.17.1-cp39-cp39-win32.whl", hash = "sha256:7294e38f9aa2e9f05f765b28ffdc5d81378508ce6dadbe93f6d464a8c9594473"}, - {file = "yarl-1.17.1-cp39-cp39-win_amd64.whl", hash = "sha256:eb6dce402734575e1a8cc0bb1509afca508a400a57ce13d306ea2c663bad1138"}, - {file = "yarl-1.17.1-py3-none-any.whl", hash = "sha256:f1790a4b1e8e8e028c391175433b9c8122c39b46e1663228158e61e6f915bf06"}, - {file = "yarl-1.17.1.tar.gz", hash = "sha256:067a63fcfda82da6b198fa73079b1ca40b7c9b7994995b6ee38acda728b64d47"}, -] - -[package.dependencies] -idna = ">=2.0" -multidict = ">=4.0" -propcache = ">=0.2.0" - [[package]] name = "zipp" version = "3.21.0" @@ -3635,10 +3073,7 @@ enabler = ["pytest-enabler (>=2.2)"] test = ["big-O", "importlib-resources", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more-itertools", "pytest (>=6,!=8.1.*)", "pytest-ignore-flaky"] type = ["pytest-mypy"] -[extras] -proxy = ["asgiproxy"] - [metadata] lock-version = "2.0" python-versions = "^3.9" -content-hash = "a2923e478d2f16aa84c5c36b4b9169e7a2d263e3b1060585bf33b9980a10324a" +content-hash = "d62cd1897d8f73e9aad9e907beb82be509dc5e33d8f37b36ebf26ad1f3075a9f" diff --git a/pyproject.toml b/pyproject.toml index fb803fbaf..eccf21230 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -49,7 +49,6 @@ setuptools = ">=75.0" httpx = ">=0.25.1,<1.0" twine = ">=4.0.0,<7.0" tomlkit = ">=0.12.4,<1.0" -asgiproxy = { version = "==0.1.1", optional = true } lazy_loader = ">=0.4" reflex-chakra = ">=0.6.0" typing_extensions = ">=4.6.0" @@ -73,14 +72,10 @@ selenium = ">=4.11.0,<5.0" pytest-benchmark = ">=4.0.0,<6.0" playwright = ">=1.46.0" pytest-playwright = ">=0.5.1" -asgiproxy = "==0.1.1" [tool.poetry.scripts] reflex = "reflex.reflex:cli" -[tool.poetry.extras] -proxy = ["asgiproxy"] - [build-system] requires = ["poetry-core>=1.5.1"] build-backend = "poetry.core.masonry.api" diff --git a/reflex/app.py b/reflex/app.py index 89ee4c164..08cb4314e 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -332,12 +332,6 @@ class App(MiddlewareMixin, LifespanMixin): self.register_lifespan_task(windows_hot_reload_lifespan_hack) - # Enable proxying to frontend server. - if not environment.REFLEX_BACKEND_ONLY.get(): - from reflex.proxy import proxy_middleware - - self.register_lifespan_task(proxy_middleware) - def _enable_state(self) -> None: """Enable state for the app.""" if not self.state: diff --git a/reflex/config.py b/reflex/config.py index 87d9ce665..0579b019f 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -26,7 +26,6 @@ from typing import ( from typing_extensions import Annotated, get_type_hints -from reflex.utils.console import set_log_level from reflex.utils.exceptions import ConfigError, EnvironmentVarValueError from reflex.utils.types import GenericType, is_union, value_inside_optional @@ -600,7 +599,6 @@ class Config(Base): class Config: """Pydantic config for the config.""" - use_enum_values = False validate_assignment = True # The name of the app (should match the name of the app directory). @@ -720,9 +718,6 @@ class Config(Base): self._non_default_attributes.update(kwargs) self._replace_defaults(**kwargs) - # Set the log level for this process - set_log_level(self.loglevel) - if ( self.state_manager_mode == constants.StateManagerMode.REDIS and not self.redis_url diff --git a/reflex/proxy.py b/reflex/proxy.py deleted file mode 100644 index 47f36c8bd..000000000 --- a/reflex/proxy.py +++ /dev/null @@ -1,119 +0,0 @@ -"""Handle proxying frontend requests from the backend server.""" - -from __future__ import annotations - -import asyncio -from contextlib import asynccontextmanager -from typing import Any, AsyncGenerator -from urllib.parse import urlparse - -from fastapi import FastAPI -from starlette.types import ASGIApp, Receive, Scope, Send - -from .config import get_config -from .utils import console - -try: - import aiohttp - from asgiproxy.config import BaseURLProxyConfigMixin, ProxyConfig - from asgiproxy.context import ProxyContext - from asgiproxy.proxies.http import proxy_http - from asgiproxy.simple_proxy import make_simple_proxy_app -except ImportError: - - @asynccontextmanager - async def proxy_middleware(*args, **kwargs) -> AsyncGenerator[None, None]: - """A no-op proxy middleware for when asgiproxy is not installed. - - Args: - *args: The positional arguments. - **kwargs: The keyword arguments. - - Yields: - None - """ - yield -else: - MAX_PROXY_RETRY = 25 - - async def proxy_http_with_retry( - *, - context: ProxyContext, - scope: Scope, - receive: Receive, - send: Send, - ) -> Any: - """Proxy an HTTP request with retries. - - Args: - context: The proxy context. - scope: The request scope. - receive: The receive channel. - send: The send channel. - - Returns: - The response from `proxy_http`. - """ - for _attempt in range(MAX_PROXY_RETRY): - try: - return await proxy_http( - context=context, - scope=scope, - receive=receive, - send=send, - ) - except aiohttp.ClientError as err: # noqa: PERF203 - console.debug( - f"Retrying request {scope['path']} due to client error {err!r}." - ) - await asyncio.sleep(0.3) - except Exception as ex: - console.debug( - f"Retrying request {scope['path']} due to unhandled exception {ex!r}." - ) - await asyncio.sleep(0.3) - - def _get_proxy_app_with_context(frontend_host: str) -> tuple[ProxyContext, ASGIApp]: - """Get the proxy app with the given frontend host. - - Args: - frontend_host: The frontend host to proxy requests to. - - Returns: - The proxy context and app. - """ - - class LocalProxyConfig(BaseURLProxyConfigMixin, ProxyConfig): - upstream_base_url = frontend_host - rewrite_host_header = urlparse(upstream_base_url).netloc - - proxy_context = ProxyContext(LocalProxyConfig()) - proxy_app = make_simple_proxy_app( - proxy_context, proxy_http_handler=proxy_http_with_retry - ) - return proxy_context, proxy_app - - @asynccontextmanager - async def proxy_middleware( # pyright: ignore[reportGeneralTypeIssues] - app: FastAPI, - ) -> AsyncGenerator[None, None]: - """A middleware to proxy requests to the separate frontend server. - - The proxy is installed on the / endpoint of the FastAPI instance. - - Args: - app: The FastAPI instance. - - Yields: - None - """ - config = get_config() - backend_port = config.backend_port - frontend_host = f"http://localhost:{config.frontend_port}" - proxy_context, proxy_app = _get_proxy_app_with_context(frontend_host) - app.mount("/", proxy_app) - console.debug( - f"Proxying '/' requests on port {backend_port} to {frontend_host}" - ) - async with proxy_context: - yield diff --git a/reflex/testing.py b/reflex/testing.py index 582d7f8c9..b3dedf398 100644 --- a/reflex/testing.py +++ b/reflex/testing.py @@ -44,7 +44,6 @@ import reflex.utils.format import reflex.utils.prerequisites import reflex.utils.processes from reflex.config import environment -from reflex.proxy import proxy_middleware from reflex.state import ( BaseState, StateManager, @@ -299,9 +298,6 @@ class AppHarness: self.state_manager = StateManagerRedis.create(self.app_instance.state) else: self.state_manager = self.app_instance._state_manager - # Disable proxy for app harness tests. - if proxy_middleware in self.app_instance.lifespan_tasks: - self.app_instance.lifespan_tasks.remove(proxy_middleware) def _reload_state_module(self): """Reload the rx.State module to avoid conflict when reloading.""" @@ -369,12 +365,9 @@ class AppHarness: def _start_frontend(self): # Set up the frontend. with chdir(self.app_path): - backend_host, backend_port = self._poll_for_servers().getsockname() config = reflex.config.get_config() - config.backend_port = backend_port config.api_url = "http://{0}:{1}".format( - backend_host, - backend_port, + *self._poll_for_servers().getsockname(), ) reflex.utils.build.setup_frontend(self.app_path) @@ -399,7 +392,6 @@ class AppHarness: self.frontend_url = m.group(1) config = reflex.config.get_config() config.deploy_url = self.frontend_url - config.frontend_port = int(self.frontend_url.rpartition(":")[2]) break if self.frontend_url is None: raise RuntimeError("Frontend did not start") @@ -923,20 +915,17 @@ class AppHarnessProd(AppHarness): root=web_root, error_page_map=error_page_map, ) as self.frontend_server: - config = reflex.config.get_config() - config.frontend_port = self.frontend_server.server_address[1] - self.frontend_url = f"http://localhost:{config.frontend_port}" + self.frontend_url = "http://localhost:{1}".format( + *self.frontend_server.socket.getsockname() + ) self.frontend_server.serve_forever() def _start_frontend(self): # Set up the frontend. with chdir(self.app_path): - backend_host, backend_port = self._poll_for_servers().getsockname() config = reflex.config.get_config() - config.backend_port = backend_port config.api_url = "http://{0}:{1}".format( - backend_host, - backend_port, + *self._poll_for_servers().getsockname(), ) reflex.reflex.export( zipping=False, diff --git a/reflex/utils/console.py b/reflex/utils/console.py index 1c08a04b6..be545140a 100644 --- a/reflex/utils/console.py +++ b/reflex/utils/console.py @@ -2,8 +2,6 @@ from __future__ import annotations -import os - from rich.console import Console from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn from rich.prompt import Prompt @@ -14,7 +12,7 @@ from reflex.constants import LogLevel _console = Console() # The current log level. -_LOG_LEVEL = LogLevel.DEFAULT +_LOG_LEVEL = LogLevel.INFO # Deprecated features who's warning has been printed. _EMITTED_DEPRECATION_WARNINGS = set() @@ -63,9 +61,6 @@ def set_log_level(log_level: LogLevel): raise ValueError(f"Invalid log level: {log_level}") from ae global _LOG_LEVEL - if log_level != _LOG_LEVEL: - # Set the loglevel persistently for subprocesses - os.environ["LOGLEVEL"] = log_level.value _LOG_LEVEL = log_level diff --git a/scripts/integration.sh b/scripts/integration.sh index 41d3051d0..dc8b5d553 100755 --- a/scripts/integration.sh +++ b/scripts/integration.sh @@ -34,4 +34,4 @@ if [ -f /proc/$pid/winpid ]; then echo "Windows detected, passing winpid $pid to port waiter" fi -python scripts/wait_for_listening_port.py $check_ports --timeout=900 --server-pid "$pid" $WAIT_FOR_LISTENING_PORT_ARGS +python scripts/wait_for_listening_port.py $check_ports --timeout=900 --server-pid "$pid" diff --git a/scripts/wait_for_listening_port.py b/scripts/wait_for_listening_port.py index 71b6dd8b2..43581f0bc 100644 --- a/scripts/wait_for_listening_port.py +++ b/scripts/wait_for_listening_port.py @@ -10,8 +10,6 @@ import time from concurrent.futures import ThreadPoolExecutor, as_completed from typing import Tuple -import httpx - # psutil is already a dependency of Reflex itself - so it's OK to use import psutil @@ -25,7 +23,6 @@ def _pid_exists(pid): return pid in psutil.pids() -# Not really used anymore now that we actually check the HTTP response. def _wait_for_port(port, server_pid, timeout) -> Tuple[bool, str]: start = time.time() print(f"Waiting for up to {timeout} seconds for port {port} to start listening.") # noqa: T201 @@ -44,70 +41,25 @@ def _wait_for_port(port, server_pid, timeout) -> Tuple[bool, str]: time.sleep(5) -def _wait_for_http_response(port, server_pid, timeout, path) -> Tuple[bool, str, str]: - start = time.time() - if path[0] != "/": - # This is a hack for passing the path on windows without a leading slash - # which mangles it https://stackoverflow.com/a/49013604 - path = "/" + path - url = f"http://localhost:{port}{path}" - print(f"Waiting for up to {timeout} seconds for {url} to return HTTP response.") # noqa: T201 - while True: - try: - if not _pid_exists(server_pid): - return False, f"Server PID {server_pid} is not running.", "" - response = httpx.get(url, timeout=0.5) - response.raise_for_status() - return ( - True, - f"{url} returned response after {time.time() - start} seconds", - response.text, - ) - except Exception as exc: # noqa: PERF203 - if time.time() - start > timeout: - return ( - False, - f"{url} still returning errors after {timeout} seconds: {exc!r}.", - "", - ) - time.sleep(5) - - def main(): """Wait for ports to start listening.""" parser = argparse.ArgumentParser(description="Wait for ports to start listening.") parser.add_argument("port", type=int, nargs="+") parser.add_argument("--timeout", type=int, required=True) parser.add_argument("--server-pid", type=int) - parser.add_argument("--path", type=str, default="/") args = parser.parse_args() - start = time.time() executor = ThreadPoolExecutor(max_workers=len(args.port)) futures = [ - executor.submit( - _wait_for_http_response, - p, - args.server_pid, - args.timeout, - args.path, - ) + executor.submit(_wait_for_port, p, args.server_pid, args.timeout) for p in args.port ] - base_content = None for f in as_completed(futures): - ok, msg, content = f.result() + ok, msg = f.result() if ok: print(f"OK: {msg}") # noqa: T201 - if base_content is None: - base_content = content - else: - assert ( - content == base_content - ), f"HTTP responses are not equal {content!r} != {base_content!r}." else: print(f"FAIL: {msg}") # noqa: T201 exit(1) - print(f"OK: All HTTP responses are equal after {time.time() - start} sec.") # noqa: T201 if __name__ == "__main__": diff --git a/tests/integration/init-test/in_docker_test_script.sh b/tests/integration/init-test/in_docker_test_script.sh index 54f821ccf..31d245588 100755 --- a/tests/integration/init-test/in_docker_test_script.sh +++ b/tests/integration/init-test/in_docker_test_script.sh @@ -29,7 +29,7 @@ source ~/venv/bin/activate pip install -U pip echo "Installing reflex from local repo code" -pip install '/reflex-repo[proxy]' +pip install /reflex-repo redis-server & From 1e7a37bcf941dec4295a82a9f8187639e95a38cf Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Fri, 10 Jan 2025 15:23:16 -0800 Subject: [PATCH 29/44] [ENG-4351] Add mapping for lucide icons (#4622) * [ENG-4351] Add mapping for lucide icons For icon names that don't auto-translate to the correct lucide tag name, provide manual override. Fix #4621 * account for new mapping in unit tests --- reflex/components/lucide/icon.py | 14 +++++++++++++- reflex/components/lucide/icon.pyi | 4 ++++ tests/units/components/lucide/test_icon.py | 10 ++++++++-- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/reflex/components/lucide/icon.py b/reflex/components/lucide/icon.py index 6b7692643..04410ac56 100644 --- a/reflex/components/lucide/icon.py +++ b/reflex/components/lucide/icon.py @@ -56,7 +56,12 @@ class Icon(LucideIconComponent): "\nSee full list at https://lucide.dev/icons." ) - props["tag"] = format.to_title_case(format.to_snake_case(props["tag"])) + "Icon" + if props["tag"] in LUCIDE_ICON_MAPPING_OVERRIDE: + props["tag"] = LUCIDE_ICON_MAPPING_OVERRIDE[props["tag"]] + else: + props["tag"] = ( + format.to_title_case(format.to_snake_case(props["tag"])) + "Icon" + ) props["alias"] = f"Lucide{props['tag']}" props.setdefault("color", "var(--current-color)") return super().create(*children, **props) @@ -1634,3 +1639,10 @@ LUCIDE_ICON_LIST = [ "zoom_in", "zoom_out", ] + +# The default transformation of some icon names doesn't match how the +# icons are exported from Lucide. Manual overrides can go here. +LUCIDE_ICON_MAPPING_OVERRIDE = { + "grid_2x_2_check": "Grid2x2Check", + "grid_2x_2_x": "Grid2x2X", +} diff --git a/reflex/components/lucide/icon.pyi b/reflex/components/lucide/icon.pyi index 61697aa2a..39a1da0e6 100644 --- a/reflex/components/lucide/icon.pyi +++ b/reflex/components/lucide/icon.pyi @@ -1682,3 +1682,7 @@ LUCIDE_ICON_LIST = [ "zoom_in", "zoom_out", ] +LUCIDE_ICON_MAPPING_OVERRIDE = { + "grid_2x_2_check": "Grid2x2Check", + "grid_2x_2_x": "Grid2x2X", +} diff --git a/tests/units/components/lucide/test_icon.py b/tests/units/components/lucide/test_icon.py index b0a3475dd..19bea7a7f 100644 --- a/tests/units/components/lucide/test_icon.py +++ b/tests/units/components/lucide/test_icon.py @@ -1,13 +1,19 @@ import pytest -from reflex.components.lucide.icon import LUCIDE_ICON_LIST, Icon +from reflex.components.lucide.icon import ( + LUCIDE_ICON_LIST, + LUCIDE_ICON_MAPPING_OVERRIDE, + Icon, +) from reflex.utils import format @pytest.mark.parametrize("tag", LUCIDE_ICON_LIST) def test_icon(tag): icon = Icon.create(tag) - assert icon.alias == f"Lucide{format.to_title_case(tag)}Icon" + assert icon.alias == "Lucide" + LUCIDE_ICON_MAPPING_OVERRIDE.get( + tag, f"{format.to_title_case(tag)}Icon" + ) def test_icon_missing_tag(): From f69be58f59b98097fecf967931b59757308e07fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= Date: Tue, 14 Jan 2025 06:02:54 -0800 Subject: [PATCH 30/44] small fix for color_mode_button (#4634) --- reflex/components/radix/themes/color_mode.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/reflex/components/radix/themes/color_mode.py b/reflex/components/radix/themes/color_mode.py index 2dd0f5e83..e93a26ef6 100644 --- a/reflex/components/radix/themes/color_mode.py +++ b/reflex/components/radix/themes/color_mode.py @@ -151,8 +151,8 @@ class ColorModeIconButton(IconButton): dropdown_menu.trigger( super().create( ColorModeIcon.create(), - **props, - ) + ), + **props, ), dropdown_menu.content( color_mode_item("light"), From e8a71122496856fbf20033aab3e3a858fe98d5c1 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Wed, 15 Jan 2025 13:04:15 -0800 Subject: [PATCH 31/44] [ENG-4383] Handle special float values on frontend (#4638) Add a test case to `test_computed_vars.py` which renders a list of special floats. Fix #4637 --- reflex/.templates/web/utils/state.js | 9 ++++++++- tests/integration/test_computed_vars.py | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/reflex/.templates/web/utils/state.js b/reflex/.templates/web/utils/state.js index 41dbee446..ec603fd13 100644 --- a/reflex/.templates/web/utils/state.js +++ b/reflex/.templates/web/utils/state.js @@ -410,7 +410,14 @@ export const connect = async ( autoUnref: false, }); // Ensure undefined fields in events are sent as null instead of removed - socket.current.io.encoder.replacer = (k, v) => (v === undefined ? null : v) + socket.current.io.encoder.replacer = (k, v) => (v === undefined ? null : v); + socket.current.io.decoder.tryParse = (str) => { + try { + return JSON5.parse(str); + } catch (e) { + return false; + } + }; function checkVisibility() { if (document.visibilityState === "visible") { diff --git a/tests/integration/test_computed_vars.py b/tests/integration/test_computed_vars.py index 03aaf18b4..efa129430 100644 --- a/tests/integration/test_computed_vars.py +++ b/tests/integration/test_computed_vars.py @@ -58,6 +58,11 @@ def ComputedVars(): def depends_on_count3(self) -> int: return self.count + # special floats should be properly decoded on the frontend + @rx.var(cache=True, initial_value=[]) + def special_floats(self) -> list[float]: + return [42.9, float("nan"), float("inf"), float("-inf")] + @rx.event def increment(self): self.count += 1 @@ -103,6 +108,11 @@ def ComputedVars(): State.depends_on_count3, id="depends_on_count3", ), + rx.text("special_floats:"), + rx.text( + State.special_floats.join(", "), + id="special_floats", + ), ), ) @@ -224,6 +234,10 @@ async def test_computed_vars( assert depends_on_count3 assert depends_on_count3.text == "0" + special_floats = driver.find_element(By.ID, "special_floats") + assert special_floats + assert special_floats.text == "42.9, NaN, Infinity, -Infinity" + increment = driver.find_element(By.ID, "increment") assert increment.is_enabled() From caf29c368068616f4e899c7d5dca8e80cb20d774 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 15 Jan 2025 14:17:58 -0800 Subject: [PATCH 32/44] put import at the top of dynamic component evaluation (#4632) --- reflex/components/dynamic.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/reflex/components/dynamic.py b/reflex/components/dynamic.py index fbfc55f97..806d610df 100644 --- a/reflex/components/dynamic.py +++ b/reflex/components/dynamic.py @@ -136,6 +136,23 @@ def load_dynamic_serializer(): module_code_lines.insert(0, "const React = window.__reflex.react;") + function_line = next( + index + for index, line in enumerate(module_code_lines) + if line.startswith("export default function") + ) + + module_code_lines = [ + line + for _, line in sorted( + enumerate(module_code_lines), + key=lambda x: ( + not (x[1].startswith("import ") and x[0] < function_line), + x[0], + ), + ) + ] + return "\n".join( [ "//__reflex_evaluate", From fbf9524a6cdca99efaa04892f01b9269a6756034 Mon Sep 17 00:00:00 2001 From: Masen Furer Date: Wed, 15 Jan 2025 14:19:11 -0800 Subject: [PATCH 33/44] Show file and line number in deprecation warnings (#4631) * Show file and line number in deprecation warnings * Exclude modules/packages by import Less bespoke method of considering some packages to be part of the framework and passed over when finding user code. --- reflex/utils/console.py | 50 ++++++++++++++++++++++++++++++++++++++--- reflex/vars/base.py | 4 ++-- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/reflex/utils/console.py b/reflex/utils/console.py index be545140a..8929b63b6 100644 --- a/reflex/utils/console.py +++ b/reflex/utils/console.py @@ -2,6 +2,11 @@ from __future__ import annotations +import inspect +import shutil +from pathlib import Path +from types import FrameType + from rich.console import Console from rich.progress import MofNCompleteColumn, Progress, TimeElapsedColumn from rich.prompt import Prompt @@ -188,6 +193,33 @@ def warn(msg: str, dedupe: bool = False, **kwargs): print(f"[orange1]Warning: {msg}[/orange1]", **kwargs) +def _get_first_non_framework_frame() -> FrameType | None: + import click + import typer + import typing_extensions + + import reflex as rx + + # Exclude utility modules that should never be the source of deprecated reflex usage. + exclude_modules = [click, rx, typer, typing_extensions] + exclude_roots = [ + p.parent.resolve() + if (p := Path(m.__file__)).name == "__init__.py" + else p.resolve() + for m in exclude_modules + ] + # Specifically exclude the reflex cli module. + if reflex_bin := shutil.which(b"reflex"): + exclude_roots.append(Path(reflex_bin.decode())) + + frame = inspect.currentframe() + while frame := frame and frame.f_back: + frame_path = Path(inspect.getfile(frame)).resolve() + if not any(frame_path.is_relative_to(root) for root in exclude_roots): + break + return frame + + def deprecate( feature_name: str, reason: str, @@ -206,15 +238,27 @@ def deprecate( dedupe: If True, suppress multiple console logs of deprecation message. kwargs: Keyword arguments to pass to the print function. """ - if feature_name not in _EMITTED_DEPRECATION_WARNINGS: + dedupe_key = feature_name + loc = "" + + # See if we can find where the deprecation exists in "user code" + origin_frame = _get_first_non_framework_frame() + if origin_frame is not None: + filename = Path(origin_frame.f_code.co_filename) + if filename.is_relative_to(Path.cwd()): + filename = filename.relative_to(Path.cwd()) + loc = f"{filename}:{origin_frame.f_lineno}" + dedupe_key = f"{dedupe_key} {loc}" + + if dedupe_key not in _EMITTED_DEPRECATION_WARNINGS: msg = ( f"{feature_name} has been deprecated in version {deprecation_version} {reason.rstrip('.')}. It will be completely " - f"removed in {removal_version}" + f"removed in {removal_version}. ({loc})" ) if _LOG_LEVEL <= LogLevel.WARNING: print(f"[yellow]DeprecationWarning: {msg}[/yellow]", **kwargs) if dedupe: - _EMITTED_DEPRECATION_WARNINGS.add(feature_name) + _EMITTED_DEPRECATION_WARNINGS.add(dedupe_key) def error(msg: str, dedupe: bool = False, **kwargs): diff --git a/reflex/vars/base.py b/reflex/vars/base.py index 0a93901cd..4a75164f4 100644 --- a/reflex/vars/base.py +++ b/reflex/vars/base.py @@ -561,7 +561,7 @@ class Var(Generic[VAR_TYPE]): if _var_is_local is not None: console.deprecate( feature_name="_var_is_local", - reason="The _var_is_local argument is not supported for Var." + reason="The _var_is_local argument is not supported for Var. " "If you want to create a Var from a raw Javascript expression, use the constructor directly", deprecation_version="0.6.0", removal_version="0.7.0", @@ -569,7 +569,7 @@ class Var(Generic[VAR_TYPE]): if _var_is_string is not None: console.deprecate( feature_name="_var_is_string", - reason="The _var_is_string argument is not supported for Var." + reason="The _var_is_string argument is not supported for Var. " "If you want to create a Var from a raw Javascript expression, use the constructor directly", deprecation_version="0.6.0", removal_version="0.7.0", From 9fe8e6f1ce519547a6d23d3f8b5d8ce16515b734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= Date: Wed, 15 Jan 2025 14:20:04 -0800 Subject: [PATCH 34/44] bump nextJS to v15 (#4630) * bump nextJS to v15 * make turbopack depends on env var --- reflex/config.py | 3 +++ reflex/constants/installer.py | 2 +- reflex/utils/prerequisites.py | 6 +++++- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/reflex/config.py b/reflex/config.py index 0579b019f..7614417d5 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -567,6 +567,9 @@ class EnvironmentVariables: # The maximum size of the reflex state in kilobytes. REFLEX_STATE_SIZE_LIMIT: EnvVar[int] = env_var(1000) + # Whether to use the turbopack bundler. + REFLEX_USE_TURBOPACK: EnvVar[bool] = env_var(True) + environment = EnvironmentVariables() diff --git a/reflex/constants/installer.py b/reflex/constants/installer.py index 0b45586dd..f9dd26b5a 100644 --- a/reflex/constants/installer.py +++ b/reflex/constants/installer.py @@ -182,7 +182,7 @@ class PackageJson(SimpleNamespace): "@emotion/react": "11.13.3", "axios": "1.7.7", "json5": "2.2.3", - "next": "14.2.16", + "next": "15.1.4", "next-sitemap": "4.2.3", "next-themes": "0.4.3", "react": "18.3.1", diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index d838c0eea..e450393c3 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -610,10 +610,14 @@ def initialize_web_directory(): init_reflex_json(project_hash=project_hash) +def _turbopack_flag() -> str: + return " --turbopack" if environment.REFLEX_USE_TURBOPACK.get() else "" + + def _compile_package_json(): return templates.PACKAGE_JSON.render( scripts={ - "dev": constants.PackageJson.Commands.DEV, + "dev": constants.PackageJson.Commands.DEV + _turbopack_flag(), "export": constants.PackageJson.Commands.EXPORT, "export_sitemap": constants.PackageJson.Commands.EXPORT_SITEMAP, "prod": constants.PackageJson.Commands.PROD, From b50b7692b22261654cc5a023d326c169bb968571 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 15 Jan 2025 14:21:33 -0800 Subject: [PATCH 35/44] improve type support for .get_state (#4623) * improve type support for .get_state * dang it darglint --- reflex/state.py | 31 ++++++++++++++++++++++++++----- reflex/utils/exceptions.py | 4 ++++ 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/reflex/state.py b/reflex/state.py index a31aae032..16875bec3 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -104,6 +104,7 @@ from reflex.utils.exceptions import ( LockExpiredError, ReflexRuntimeError, SetUndefinedStateVarError, + StateMismatchError, StateSchemaMismatchError, StateSerializationError, StateTooLargeError, @@ -1543,7 +1544,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): # Return the direct parent of target_state_cls for subsequent linking. return parent_state - def _get_state_from_cache(self, state_cls: Type[BaseState]) -> BaseState: + def _get_state_from_cache(self, state_cls: Type[T_STATE]) -> T_STATE: """Get a state instance from the cache. Args: @@ -1551,11 +1552,19 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): Returns: The instance of state_cls associated with this state's client_token. + + Raises: + StateMismatchError: If the state instance is not of the expected type. """ root_state = self._get_root_state() - return root_state.get_substate(state_cls.get_full_name().split(".")) + substate = root_state.get_substate(state_cls.get_full_name().split(".")) + if not isinstance(substate, state_cls): + raise StateMismatchError( + f"Searched for state {state_cls.get_full_name()} but found {substate}." + ) + return substate - async def _get_state_from_redis(self, state_cls: Type[BaseState]) -> BaseState: + async def _get_state_from_redis(self, state_cls: Type[T_STATE]) -> T_STATE: """Get a state instance from redis. Args: @@ -1566,6 +1575,7 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): Raises: RuntimeError: If redis is not used in this backend process. + StateMismatchError: If the state instance is not of the expected type. """ # Fetch all missing parent states from redis. parent_state_of_state_cls = await self._populate_parent_states(state_cls) @@ -1577,14 +1587,22 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): f"Requested state {state_cls.get_full_name()} is not cached and cannot be accessed without redis. " "(All states should already be available -- this is likely a bug).", ) - return await state_manager.get_state( + + state_in_redis = await state_manager.get_state( token=_substate_key(self.router.session.client_token, state_cls), top_level=False, get_substates=True, parent_state=parent_state_of_state_cls, ) - async def get_state(self, state_cls: Type[BaseState]) -> BaseState: + if not isinstance(state_in_redis, state_cls): + raise StateMismatchError( + f"Searched for state {state_cls.get_full_name()} but found {state_in_redis}." + ) + + return state_in_redis + + async def get_state(self, state_cls: Type[T_STATE]) -> T_STATE: """Get an instance of the state associated with this token. Allows for arbitrary access to sibling states from within an event handler. @@ -2316,6 +2334,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): return state +T_STATE = TypeVar("T_STATE", bound=BaseState) + + class State(BaseState): """The app Base State.""" diff --git a/reflex/utils/exceptions.py b/reflex/utils/exceptions.py index bceadc977..339abcda1 100644 --- a/reflex/utils/exceptions.py +++ b/reflex/utils/exceptions.py @@ -163,6 +163,10 @@ class StateSerializationError(ReflexError): """Raised when the state cannot be serialized.""" +class StateMismatchError(ReflexError, ValueError): + """Raised when the state retrieved does not match the expected state.""" + + class SystemPackageMissingError(ReflexError): """Raised when a system package is missing.""" From cb24492371d8e3bb90ab6c924f5455b925279fd7 Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Wed, 15 Jan 2025 14:23:45 -0800 Subject: [PATCH 36/44] fix boolean to boolen comparisons (#4620) * fix boolean to boolen comparisons * fixes #4618 * fix tests --- reflex/vars/number.py | 23 ++++++----------------- tests/units/test_var.py | 2 +- 2 files changed, 7 insertions(+), 18 deletions(-) diff --git a/reflex/vars/number.py b/reflex/vars/number.py index d04aded35..a2a0293d5 100644 --- a/reflex/vars/number.py +++ b/reflex/vars/number.py @@ -20,7 +20,6 @@ from typing import ( from reflex.constants.base import Dirs from reflex.utils.exceptions import PrimitiveUnserializableToJSON, VarTypeError from reflex.utils.imports import ImportDict, ImportVar -from reflex.utils.types import is_optional from .base import ( CustomVarOperationReturn, @@ -431,7 +430,7 @@ class NumberVar(Var[NUMBER_T], python_types=(int, float)): """ if not isinstance(other, NUMBER_TYPES): raise_unsupported_operand_types("<", (type(self), type(other))) - return less_than_operation(self, +other) + return less_than_operation(+self, +other) @overload def __le__(self, other: number_types) -> BooleanVar: ... @@ -450,7 +449,7 @@ class NumberVar(Var[NUMBER_T], python_types=(int, float)): """ if not isinstance(other, NUMBER_TYPES): raise_unsupported_operand_types("<=", (type(self), type(other))) - return less_than_or_equal_operation(self, +other) + return less_than_or_equal_operation(+self, +other) def __eq__(self, other: Any): """Equal comparison. @@ -462,7 +461,7 @@ class NumberVar(Var[NUMBER_T], python_types=(int, float)): The result of the comparison. """ if isinstance(other, NUMBER_TYPES): - return equal_operation(self, +other) + return equal_operation(+self, +other) return equal_operation(self, other) def __ne__(self, other: Any): @@ -475,7 +474,7 @@ class NumberVar(Var[NUMBER_T], python_types=(int, float)): The result of the comparison. """ if isinstance(other, NUMBER_TYPES): - return not_equal_operation(self, +other) + return not_equal_operation(+self, +other) return not_equal_operation(self, other) @overload @@ -495,7 +494,7 @@ class NumberVar(Var[NUMBER_T], python_types=(int, float)): """ if not isinstance(other, NUMBER_TYPES): raise_unsupported_operand_types(">", (type(self), type(other))) - return greater_than_operation(self, +other) + return greater_than_operation(+self, +other) @overload def __ge__(self, other: number_types) -> BooleanVar: ... @@ -514,17 +513,7 @@ class NumberVar(Var[NUMBER_T], python_types=(int, float)): """ if not isinstance(other, NUMBER_TYPES): raise_unsupported_operand_types(">=", (type(self), type(other))) - return greater_than_or_equal_operation(self, +other) - - def bool(self): - """Boolean conversion. - - Returns: - The boolean value of the number. - """ - if is_optional(self._var_type): - return boolify((self != None) & (self != 0)) # noqa: E711 - return self != 0 + return greater_than_or_equal_operation(+self, +other) def _is_strict_float(self) -> bool: """Check if the number is a float. diff --git a/tests/units/test_var.py b/tests/units/test_var.py index bfa8aa35a..e5f5bbb2a 100644 --- a/tests/units/test_var.py +++ b/tests/units/test_var.py @@ -1004,7 +1004,7 @@ def test_all_number_operations(): assert ( str(even_more_complicated_number) - == "!(((Math.abs(Math.floor(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2))) || (2 && Math.round(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2)))) !== 0))" + == "!(isTrue((Math.abs(Math.floor(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2))) || (2 && Math.round(((Math.floor(((-((-5.4 + 1)) * 2) / 3) / 2) % 3) ** 2))))))" ) assert str(LiteralNumberVar.create(5) > False) == "(5 > 0)" From e8dd0ae47db200f49cfec7b05099f04e8250923b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= Date: Wed, 15 Jan 2025 15:57:50 -0800 Subject: [PATCH 37/44] don't need node when --backend_only is used (#4641) --- reflex/reflex.py | 6 ++++-- reflex/utils/processes.py | 29 ++++++++++++++++++----------- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/reflex/reflex.py b/reflex/reflex.py index 22fcb9fb8..b0f4ccd91 100644 --- a/reflex/reflex.py +++ b/reflex/reflex.py @@ -519,7 +519,9 @@ def deploy( if prerequisites.needs_reinit(frontend=True): _init(name=config.app_name, loglevel=loglevel) prerequisites.check_latest_package_version(constants.ReflexHostingCLI.MODULE_NAME) - + extra: dict[str, str] = ( + {"config_path": config_path} if config_path is not None else {} + ) hosting_cli.deploy( app_name=app_name, export_fn=lambda zip_dest_dir, @@ -545,7 +547,7 @@ def deploy( loglevel=type(loglevel).INFO, # type: ignore token=token, project=project, - config_path=config_path, + **extra, ) diff --git a/reflex/utils/processes.py b/reflex/utils/processes.py index 871b5f323..3673b36b2 100644 --- a/reflex/utils/processes.py +++ b/reflex/utils/processes.py @@ -17,6 +17,7 @@ import typer from redis.exceptions import RedisError from reflex import constants +from reflex.config import environment from reflex.utils import console, path_ops, prerequisites @@ -156,24 +157,30 @@ def new_process(args, run: bool = False, show_logs: bool = False, **kwargs): Raises: Exit: When attempting to run a command with a None value. """ - node_bin_path = str(path_ops.get_node_bin_path()) - if not node_bin_path and not prerequisites.CURRENTLY_INSTALLING_NODE: - console.warn( - "The path to the Node binary could not be found. Please ensure that Node is properly " - "installed and added to your system's PATH environment variable or try running " - "`reflex init` again." - ) + # Check for invalid command first. if None in args: console.error(f"Invalid command: {args}") raise typer.Exit(1) - # Add the node bin path to the PATH environment variable. + + path_env: str = os.environ.get("PATH", "") + + # Add node_bin_path to the PATH environment variable. + if not environment.REFLEX_BACKEND_ONLY.get(): + node_bin_path = str(path_ops.get_node_bin_path()) + if not node_bin_path and not prerequisites.CURRENTLY_INSTALLING_NODE: + console.warn( + "The path to the Node binary could not be found. Please ensure that Node is properly " + "installed and added to your system's PATH environment variable or try running " + "`reflex init` again." + ) + path_env = os.pathsep.join([node_bin_path, path_env]) + env: dict[str, str] = { **os.environ, - "PATH": os.pathsep.join( - [node_bin_path if node_bin_path else "", os.environ["PATH"]] - ), # type: ignore + "PATH": path_env, **kwargs.pop("env", {}), } + kwargs = { "env": env, "stderr": None if show_logs else subprocess.STDOUT, From c8de356d98eff2db13e8295aaff2b791e1a7267e Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Thu, 16 Jan 2025 11:22:56 -0800 Subject: [PATCH 38/44] cast return_expr to Var all the time (#4649) --- reflex/vars/function.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/reflex/vars/function.py b/reflex/vars/function.py index 2a7d50e1b..131f15b9f 100644 --- a/reflex/vars/function.py +++ b/reflex/vars/function.py @@ -390,6 +390,7 @@ class ArgsFunctionOperation(CachedVarOperation, FunctionVar): Returns: The function var. """ + return_expr = Var.create(return_expr) return cls( _js_expr="", _var_type=_var_type, @@ -445,6 +446,7 @@ class ArgsFunctionOperationBuilder(CachedVarOperation, BuilderFunctionVar): Returns: The function var. """ + return_expr = Var.create(return_expr) return cls( _js_expr="", _var_type=_var_type, From 6e546526b4c860d0e5955cb543bfdba4973aa9e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= Date: Thu, 16 Jan 2025 12:49:54 -0800 Subject: [PATCH 39/44] computed var default to cache=True (#4194) * computed var default to cache=True * fix lifespan integration tests * fix redis test --- reflex/state.py | 1 - reflex/vars/base.py | 17 ++---- tests/integration/test_computed_vars.py | 17 +++--- tests/integration/test_dynamic_routes.py | 6 +- tests/integration/test_lifespan.py | 6 +- tests/integration/test_media.py | 14 ++--- tests/units/test_app.py | 8 +-- tests/units/test_state.py | 74 ++++++++++-------------- tests/units/test_state_tree.py | 8 +-- tests/units/test_var.py | 10 +--- 10 files changed, 68 insertions(+), 93 deletions(-) diff --git a/reflex/state.py b/reflex/state.py index 16875bec3..e15c73978 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -1200,7 +1200,6 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): fget=func, auto_deps=False, deps=["router"], - cache=True, _js_expr=param, _var_data=VarData.from_state(cls), ) diff --git a/reflex/vars/base.py b/reflex/vars/base.py index 4a75164f4..2892d004d 100644 --- a/reflex/vars/base.py +++ b/reflex/vars/base.py @@ -1838,7 +1838,7 @@ class ComputedVar(Var[RETURN_TYPE]): self, fget: Callable[[BASE_STATE], RETURN_TYPE], initial_value: RETURN_TYPE | types.Unset = types.Unset(), - cache: bool = False, + cache: bool = True, deps: Optional[List[Union[str, Var]]] = None, auto_deps: bool = True, interval: Optional[Union[int, datetime.timedelta]] = None, @@ -2253,7 +2253,7 @@ if TYPE_CHECKING: def computed_var( fget: None = None, initial_value: Any | types.Unset = types.Unset(), - cache: bool = False, + cache: bool = True, deps: Optional[List[Union[str, Var]]] = None, auto_deps: bool = True, interval: Optional[Union[datetime.timedelta, int]] = None, @@ -2266,7 +2266,7 @@ def computed_var( def computed_var( fget: Callable[[BASE_STATE], RETURN_TYPE], initial_value: RETURN_TYPE | types.Unset = types.Unset(), - cache: bool = False, + cache: bool = True, deps: Optional[List[Union[str, Var]]] = None, auto_deps: bool = True, interval: Optional[Union[datetime.timedelta, int]] = None, @@ -2278,7 +2278,7 @@ def computed_var( def computed_var( fget: Callable[[BASE_STATE], Any] | None = None, initial_value: Any | types.Unset = types.Unset(), - cache: Optional[bool] = None, + cache: bool = True, deps: Optional[List[Union[str, Var]]] = None, auto_deps: bool = True, interval: Optional[Union[datetime.timedelta, int]] = None, @@ -2304,15 +2304,6 @@ def computed_var( ValueError: If caching is disabled and an update interval is set. VarDependencyError: If user supplies dependencies without caching. """ - if cache is None: - cache = False - console.deprecate( - "Default non-cached rx.var", - "the default value will be `@rx.var(cache=True)` in a future release. " - "To retain uncached var, explicitly pass `@rx.var(cache=False)`", - deprecation_version="0.6.8", - removal_version="0.7.0", - ) if cache is False and interval is not None: raise ValueError("Cannot set update interval without caching.") diff --git a/tests/integration/test_computed_vars.py b/tests/integration/test_computed_vars.py index efa129430..f56001ea8 100644 --- a/tests/integration/test_computed_vars.py +++ b/tests/integration/test_computed_vars.py @@ -22,22 +22,22 @@ def ComputedVars(): count: int = 0 # cached var with dep on count - @rx.var(cache=True, interval=15) + @rx.var(interval=15) def count1(self) -> int: return self.count # cached backend var with dep on count - @rx.var(cache=True, interval=15, backend=True) + @rx.var(interval=15, backend=True) def count1_backend(self) -> int: return self.count # same as above but implicit backend with `_` prefix - @rx.var(cache=True, interval=15) + @rx.var(interval=15) def _count1_backend(self) -> int: return self.count # explicit disabled auto_deps - @rx.var(interval=15, cache=True, auto_deps=False) + @rx.var(interval=15, auto_deps=False) def count3(self) -> int: # this will not add deps, because auto_deps is False print(self.count1) @@ -45,16 +45,19 @@ def ComputedVars(): return self.count # explicit dependency on count var - @rx.var(cache=True, deps=["count"], auto_deps=False) + @rx.var(deps=["count"], auto_deps=False) def depends_on_count(self) -> int: return self.count # explicit dependency on count1 var - @rx.var(cache=True, deps=[count1], auto_deps=False) + @rx.var(deps=[count1], auto_deps=False) def depends_on_count1(self) -> int: return self.count - @rx.var(deps=[count3], auto_deps=False, cache=True) + @rx.var( + deps=[count3], + auto_deps=False, + ) def depends_on_count3(self) -> int: return self.count diff --git a/tests/integration/test_dynamic_routes.py b/tests/integration/test_dynamic_routes.py index 8a3cde3a2..c210bde69 100644 --- a/tests/integration/test_dynamic_routes.py +++ b/tests/integration/test_dynamic_routes.py @@ -74,16 +74,16 @@ def DynamicRoute(): class ArgState(rx.State): """The app state.""" - @rx.var + @rx.var(cache=False) def arg(self) -> int: return int(self.arg_str or 0) class ArgSubState(ArgState): - @rx.var(cache=True) + @rx.var def cached_arg(self) -> int: return self.arg - @rx.var(cache=True) + @rx.var def cached_arg_str(self) -> str: return self.arg_str diff --git a/tests/integration/test_lifespan.py b/tests/integration/test_lifespan.py index 0fa4a7e92..d79273fbc 100644 --- a/tests/integration/test_lifespan.py +++ b/tests/integration/test_lifespan.py @@ -36,7 +36,7 @@ def LifespanApp(): print("Lifespan global started.") try: while True: - lifespan_task_global += inc # pyright: ignore[reportUnboundVariable] + lifespan_task_global += inc # pyright: ignore[reportUnboundVariable, reportPossiblyUnboundVariable] await asyncio.sleep(0.1) except asyncio.CancelledError as ce: print(f"Lifespan global cancelled: {ce}.") @@ -45,11 +45,11 @@ def LifespanApp(): class LifespanState(rx.State): interval: int = 100 - @rx.var + @rx.var(cache=False) def task_global(self) -> int: return lifespan_task_global - @rx.var + @rx.var(cache=False) def context_global(self) -> int: return lifespan_context_global diff --git a/tests/integration/test_media.py b/tests/integration/test_media.py index 10af26591..649038a7e 100644 --- a/tests/integration/test_media.py +++ b/tests/integration/test_media.py @@ -22,31 +22,31 @@ def MediaApp(): img.format = format # type: ignore return img - @rx.var(cache=True) + @rx.var def img_default(self) -> Image.Image: return self._blue() - @rx.var(cache=True) + @rx.var def img_bmp(self) -> Image.Image: return self._blue(format="BMP") - @rx.var(cache=True) + @rx.var def img_jpg(self) -> Image.Image: return self._blue(format="JPEG") - @rx.var(cache=True) + @rx.var def img_png(self) -> Image.Image: return self._blue(format="PNG") - @rx.var(cache=True) + @rx.var def img_gif(self) -> Image.Image: return self._blue(format="GIF") - @rx.var(cache=True) + @rx.var def img_webp(self) -> Image.Image: return self._blue(format="WEBP") - @rx.var(cache=True) + @rx.var def img_from_url(self) -> Image.Image: img_url = "https://picsum.photos/id/1/200/300" img_resp = httpx.get(img_url, follow_redirects=True) diff --git a/tests/units/test_app.py b/tests/units/test_app.py index 48a4bdda1..f805f83ec 100644 --- a/tests/units/test_app.py +++ b/tests/units/test_app.py @@ -908,7 +908,7 @@ class DynamicState(BaseState): """Increment the counter var.""" self.counter = self.counter + 1 - @computed_var(cache=True) + @computed_var def comp_dynamic(self) -> str: """A computed var that depends on the dynamic var. @@ -1549,11 +1549,11 @@ def test_app_with_valid_var_dependencies(compilable_app: tuple[App, Path]): base: int = 0 _backend: int = 0 - @computed_var(cache=True) + @computed_var() def foo(self) -> str: return "foo" - @computed_var(deps=["_backend", "base", foo], cache=True) + @computed_var(deps=["_backend", "base", foo]) def bar(self) -> str: return "bar" @@ -1565,7 +1565,7 @@ def test_app_with_invalid_var_dependencies(compilable_app: tuple[App, Path]): app, _ = compilable_app class InvalidDepState(BaseState): - @computed_var(deps=["foolksjdf"], cache=True) + @computed_var(deps=["foolksjdf"]) def bar(self) -> str: return "bar" diff --git a/tests/units/test_state.py b/tests/units/test_state.py index 41fac443e..19f3e4239 100644 --- a/tests/units/test_state.py +++ b/tests/units/test_state.py @@ -202,7 +202,7 @@ class GrandchildState(ChildState): class GrandchildState2(ChildState2): """A grandchild state fixture.""" - @rx.var(cache=True) + @rx.var def cached(self) -> str: """A cached var. @@ -215,7 +215,7 @@ class GrandchildState2(ChildState2): class GrandchildState3(ChildState3): """A great grandchild state fixture.""" - @rx.var + @rx.var(cache=False) def computed(self) -> str: """A computed var. @@ -796,7 +796,7 @@ async def test_process_event_simple(test_state): # The delta should contain the changes, including computed vars. assert update.delta == { - TestState.get_full_name(): {"num1": 69, "sum": 72.14, "upper": ""}, + TestState.get_full_name(): {"num1": 69, "sum": 72.14}, GrandchildState3.get_full_name(): {"computed": ""}, } assert update.events == [] @@ -823,7 +823,7 @@ async def test_process_event_substate(test_state, child_state, grandchild_state) assert child_state.value == "HI" assert child_state.count == 24 assert update.delta == { - TestState.get_full_name(): {"sum": 3.14, "upper": ""}, + # TestState.get_full_name(): {"sum": 3.14, "upper": ""}, ChildState.get_full_name(): {"value": "HI", "count": 24}, GrandchildState3.get_full_name(): {"computed": ""}, } @@ -839,7 +839,7 @@ async def test_process_event_substate(test_state, child_state, grandchild_state) update = await test_state._process(event).__anext__() assert grandchild_state.value2 == "new" assert update.delta == { - TestState.get_full_name(): {"sum": 3.14, "upper": ""}, + # TestState.get_full_name(): {"sum": 3.14, "upper": ""}, GrandchildState.get_full_name(): {"value2": "new"}, GrandchildState3.get_full_name(): {"computed": ""}, } @@ -989,7 +989,7 @@ class InterdependentState(BaseState): v1: int = 0 _v2: int = 1 - @rx.var(cache=True) + @rx.var def v1x2(self) -> int: """Depends on var v1. @@ -998,7 +998,7 @@ class InterdependentState(BaseState): """ return self.v1 * 2 - @rx.var(cache=True) + @rx.var def v2x2(self) -> int: """Depends on backend var _v2. @@ -1007,7 +1007,7 @@ class InterdependentState(BaseState): """ return self._v2 * 2 - @rx.var(cache=True, backend=True) + @rx.var(backend=True) def v2x2_backend(self) -> int: """Depends on backend var _v2. @@ -1016,7 +1016,7 @@ class InterdependentState(BaseState): """ return self._v2 * 2 - @rx.var(cache=True) + @rx.var def v1x2x2(self) -> int: """Depends on ComputedVar v1x2. @@ -1025,7 +1025,7 @@ class InterdependentState(BaseState): """ return self.v1x2 * 2 # type: ignore - @rx.var(cache=True) + @rx.var def _v3(self) -> int: """Depends on backend var _v2. @@ -1034,7 +1034,7 @@ class InterdependentState(BaseState): """ return self._v2 - @rx.var(cache=True) + @rx.var def v3x2(self) -> int: """Depends on ComputedVar _v3. @@ -1239,7 +1239,7 @@ def test_computed_var_cached(): class ComputedState(BaseState): v: int = 0 - @rx.var(cache=True) + @rx.var def comp_v(self) -> int: nonlocal comp_v_calls comp_v_calls += 1 @@ -1264,15 +1264,15 @@ def test_computed_var_cached_depends_on_non_cached(): class ComputedState(BaseState): v: int = 0 - @rx.var + @rx.var(cache=False) def no_cache_v(self) -> int: return self.v - @rx.var(cache=True) + @rx.var def dep_v(self) -> int: return self.no_cache_v # type: ignore - @rx.var(cache=True) + @rx.var def comp_v(self) -> int: return self.v @@ -1304,14 +1304,14 @@ def test_computed_var_depends_on_parent_non_cached(): counter = 0 class ParentState(BaseState): - @rx.var + @rx.var(cache=False) def no_cache_v(self) -> int: nonlocal counter counter += 1 return counter class ChildState(ParentState): - @rx.var(cache=True) + @rx.var def dep_v(self) -> int: return self.no_cache_v # type: ignore @@ -1357,7 +1357,7 @@ def test_cached_var_depends_on_event_handler(use_partial: bool): def handler(self): self.x = self.x + 1 - @rx.var(cache=True) + @rx.var def cached_x_side_effect(self) -> int: self.handler() nonlocal counter @@ -1393,7 +1393,7 @@ def test_computed_var_dependencies(): def testprop(self) -> int: return self.v - @rx.var(cache=True) + @rx.var def comp_v(self) -> int: """Direct access. @@ -1402,7 +1402,7 @@ def test_computed_var_dependencies(): """ return self.v - @rx.var(cache=True, backend=True) + @rx.var(backend=True) def comp_v_backend(self) -> int: """Direct access backend var. @@ -1411,7 +1411,7 @@ def test_computed_var_dependencies(): """ return self.v - @rx.var(cache=True) + @rx.var def comp_v_via_property(self) -> int: """Access v via property. @@ -1420,7 +1420,7 @@ def test_computed_var_dependencies(): """ return self.testprop - @rx.var(cache=True) + @rx.var def comp_w(self): """Nested lambda. @@ -1429,7 +1429,7 @@ def test_computed_var_dependencies(): """ return lambda: self.w - @rx.var(cache=True) + @rx.var def comp_x(self): """Nested function. @@ -1442,7 +1442,7 @@ def test_computed_var_dependencies(): return _ - @rx.var(cache=True) + @rx.var def comp_y(self) -> List[int]: """Comprehension iterating over attribute. @@ -1451,7 +1451,7 @@ def test_computed_var_dependencies(): """ return [round(y) for y in self.y] - @rx.var(cache=True) + @rx.var def comp_z(self) -> List[bool]: """Comprehension accesses attribute. @@ -2027,10 +2027,6 @@ async def test_state_proxy(grandchild_state: GrandchildState, mock_app: rx.App): assert mcall.args[0] == str(SocketEvent.EVENT) assert mcall.args[1] == StateUpdate( delta={ - parent_state.get_full_name(): { - "upper": "", - "sum": 3.14, - }, grandchild_state.get_full_name(): { "value2": "42", }, @@ -2053,7 +2049,7 @@ class BackgroundTaskState(BaseState): super().__init__(**kwargs) self.router_data = {"simulate": "hydrate"} - @rx.var + @rx.var(cache=False) def computed_order(self) -> List[str]: """Get the order as a computed var. @@ -3040,10 +3036,6 @@ async def test_get_state(mock_app: rx.App, token: str): grandchild_state.value2 = "set_value" assert test_state.get_delta() == { - TestState.get_full_name(): { - "sum": 3.14, - "upper": "", - }, GrandchildState.get_full_name(): { "value2": "set_value", }, @@ -3081,10 +3073,6 @@ async def test_get_state(mock_app: rx.App, token: str): child_state2.value = "set_c2_value" assert new_test_state.get_delta() == { - TestState.get_full_name(): { - "sum": 3.14, - "upper": "", - }, ChildState2.get_full_name(): { "value": "set_c2_value", }, @@ -3139,7 +3127,7 @@ async def test_get_state_from_sibling_not_cached(mock_app: rx.App, token: str): child3_var: int = 0 - @rx.var + @rx.var(cache=False) def v(self): pass @@ -3210,8 +3198,8 @@ def test_potentially_dirty_substates(): def bar(self) -> str: return "" - assert RxState._potentially_dirty_substates() == {State} - assert State._potentially_dirty_substates() == {C1} + assert RxState._potentially_dirty_substates() == set() + assert State._potentially_dirty_substates() == set() assert C1._potentially_dirty_substates() == set() @@ -3226,7 +3214,7 @@ def test_router_var_dep() -> None: class RouterVarDepState(RouterVarParentState): """A state with a router var dependency.""" - @rx.var(cache=True) + @rx.var def foo(self) -> str: return self.router.page.params.get("foo", "") @@ -3421,7 +3409,7 @@ class MixinState(State, mixin=True): _backend: int = 0 _backend_no_default: dict - @rx.var(cache=True) + @rx.var def computed(self) -> str: """A computed var on mixin state. diff --git a/tests/units/test_state_tree.py b/tests/units/test_state_tree.py index ebdd877de..6fe828819 100644 --- a/tests/units/test_state_tree.py +++ b/tests/units/test_state_tree.py @@ -42,7 +42,7 @@ class SubA_A_A_A(SubA_A_A): class SubA_A_A_B(SubA_A_A): """SubA_A_A_B is a child of SubA_A_A.""" - @rx.var(cache=True) + @rx.var def sub_a_a_a_cached(self) -> int: """A cached var. @@ -117,7 +117,7 @@ class TreeD(Root): d: int - @rx.var + @rx.var(cache=False) def d_var(self) -> int: """A computed var. @@ -156,7 +156,7 @@ class SubE_A_A_A_A(SubE_A_A_A): sub_e_a_a_a_a: int - @rx.var + @rx.var(cache=False) def sub_e_a_a_a_a_var(self) -> int: """A computed var. @@ -183,7 +183,7 @@ class SubE_A_A_A_D(SubE_A_A_A): sub_e_a_a_a_d: int - @rx.var(cache=True) + @rx.var def sub_e_a_a_a_d_var(self) -> int: """A computed var. diff --git a/tests/units/test_var.py b/tests/units/test_var.py index e5f5bbb2a..6ad82a761 100644 --- a/tests/units/test_var.py +++ b/tests/units/test_var.py @@ -1814,10 +1814,7 @@ def cv_fget(state: BaseState) -> int: ], ) def test_computed_var_deps(deps: List[Union[str, Var]], expected: Set[str]): - @computed_var( - deps=deps, - cache=True, - ) + @computed_var(deps=deps) def test_var(state) -> int: return 1 @@ -1835,10 +1832,7 @@ def test_computed_var_deps(deps: List[Union[str, Var]], expected: Set[str]): def test_invalid_computed_var_deps(deps: List): with pytest.raises(TypeError): - @computed_var( - deps=deps, - cache=True, - ) + @computed_var(deps=deps) def test_var(state) -> int: return 1 From 4da32a122baeb47093cc1ea07e4c02ea42351084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= Date: Fri, 17 Jan 2025 16:43:11 -0800 Subject: [PATCH 40/44] allow dynamic icons name (#4636) * allow dynamic icons name * handle literal vars * clean up code --- reflex/components/lucide/icon.py | 50 +++++++++++++++++++++++-------- reflex/components/lucide/icon.pyi | 50 +++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 12 deletions(-) diff --git a/reflex/components/lucide/icon.py b/reflex/components/lucide/icon.py index 04410ac56..6c7cbede7 100644 --- a/reflex/components/lucide/icon.py +++ b/reflex/components/lucide/icon.py @@ -2,13 +2,15 @@ from reflex.components.component import Component from reflex.utils import format -from reflex.vars.base import Var +from reflex.utils.imports import ImportVar +from reflex.vars.base import LiteralVar, Var +from reflex.vars.sequence import LiteralStringVar class LucideIconComponent(Component): """Lucide Icon Component.""" - library = "lucide-react@0.469.0" + library = "lucide-react@0.471.1" class Icon(LucideIconComponent): @@ -32,6 +34,7 @@ class Icon(LucideIconComponent): Raises: AttributeError: The errors tied to bad usage of the Icon component. ValueError: If the icon tag is invalid. + TypeError: If the icon name is not a string. Returns: The created component. @@ -39,7 +42,6 @@ class Icon(LucideIconComponent): if children: if len(children) == 1 and isinstance(children[0], str): props["tag"] = children[0] - children = [] else: raise AttributeError( f"Passing multiple children to Icon component is not allowed: remove positional arguments {children[1:]} to fix" @@ -47,24 +49,46 @@ class Icon(LucideIconComponent): if "tag" not in props: raise AttributeError("Missing 'tag' keyword-argument for Icon") + tag: str | Var | LiteralVar = props.pop("tag") + if isinstance(tag, LiteralVar): + if isinstance(tag, LiteralStringVar): + tag = tag._var_value + else: + raise TypeError(f"Icon name must be a string, got {type(tag)}") + elif isinstance(tag, Var): + return DynamicIcon.create(name=tag, **props) + if ( - not isinstance(props["tag"], str) - or format.to_snake_case(props["tag"]) not in LUCIDE_ICON_LIST + not isinstance(tag, str) + or format.to_snake_case(tag) not in LUCIDE_ICON_LIST ): raise ValueError( - f"Invalid icon tag: {props['tag']}. Please use one of the following: {', '.join(LUCIDE_ICON_LIST[0:25])}, ..." + f"Invalid icon tag: {tag}. Please use one of the following: {', '.join(LUCIDE_ICON_LIST[0:25])}, ..." "\nSee full list at https://lucide.dev/icons." ) - if props["tag"] in LUCIDE_ICON_MAPPING_OVERRIDE: - props["tag"] = LUCIDE_ICON_MAPPING_OVERRIDE[props["tag"]] + if tag in LUCIDE_ICON_MAPPING_OVERRIDE: + props["tag"] = LUCIDE_ICON_MAPPING_OVERRIDE[tag] else: - props["tag"] = ( - format.to_title_case(format.to_snake_case(props["tag"])) + "Icon" - ) + props["tag"] = format.to_title_case(format.to_snake_case(tag)) + "Icon" props["alias"] = f"Lucide{props['tag']}" props.setdefault("color", "var(--current-color)") - return super().create(*children, **props) + return super().create(**props) + + +class DynamicIcon(LucideIconComponent): + """A DynamicIcon component.""" + + tag = "DynamicIcon" + + name: Var[str] + + def _get_imports(self): + _imports = super()._get_imports() + if self.library: + _imports.pop(self.library) + _imports["lucide-react/dynamic"] = [ImportVar("DynamicIcon", install=False)] + return _imports LUCIDE_ICON_LIST = [ @@ -846,6 +870,7 @@ LUCIDE_ICON_LIST = [ "house", "house_plug", "house_plus", + "house_wifi", "ice_cream_bowl", "ice_cream_cone", "id_card", @@ -1534,6 +1559,7 @@ LUCIDE_ICON_LIST = [ "trending_up_down", "triangle", "triangle_alert", + "triangle_dashed", "triangle_right", "trophy", "truck", diff --git a/reflex/components/lucide/icon.pyi b/reflex/components/lucide/icon.pyi index 39a1da0e6..6094cfd87 100644 --- a/reflex/components/lucide/icon.pyi +++ b/reflex/components/lucide/icon.pyi @@ -104,12 +104,60 @@ class Icon(LucideIconComponent): Raises: AttributeError: The errors tied to bad usage of the Icon component. ValueError: If the icon tag is invalid. + TypeError: If the icon name is not a string. Returns: The created component. """ ... +class DynamicIcon(LucideIconComponent): + @overload + @classmethod + def create( # type: ignore + cls, + *children, + name: Optional[Union[Var[str], str]] = None, + style: Optional[Style] = None, + key: Optional[Any] = None, + id: Optional[Any] = None, + class_name: Optional[Any] = None, + autofocus: Optional[bool] = None, + custom_attrs: Optional[Dict[str, Union[Var, Any]]] = None, + on_blur: Optional[EventType[[], BASE_STATE]] = None, + on_click: Optional[EventType[[], BASE_STATE]] = None, + on_context_menu: Optional[EventType[[], BASE_STATE]] = None, + on_double_click: Optional[EventType[[], BASE_STATE]] = None, + on_focus: Optional[EventType[[], BASE_STATE]] = None, + on_mount: Optional[EventType[[], BASE_STATE]] = None, + on_mouse_down: Optional[EventType[[], BASE_STATE]] = None, + on_mouse_enter: Optional[EventType[[], BASE_STATE]] = None, + on_mouse_leave: Optional[EventType[[], BASE_STATE]] = None, + on_mouse_move: Optional[EventType[[], BASE_STATE]] = None, + on_mouse_out: Optional[EventType[[], BASE_STATE]] = None, + on_mouse_over: Optional[EventType[[], BASE_STATE]] = None, + on_mouse_up: Optional[EventType[[], BASE_STATE]] = None, + on_scroll: Optional[EventType[[], BASE_STATE]] = None, + on_unmount: Optional[EventType[[], BASE_STATE]] = None, + **props, + ) -> "DynamicIcon": + """Create the component. + + Args: + *children: The children of the component. + style: The style of the component. + key: A unique key for the component. + id: The id for the component. + class_name: The class name for the component. + autofocus: Whether the component should take the focus once the page is loaded + custom_attrs: custom attribute + **props: The props of the component. + + Returns: + The component. + """ + ... + LUCIDE_ICON_LIST = [ "a_arrow_down", "a_arrow_up", @@ -889,6 +937,7 @@ LUCIDE_ICON_LIST = [ "house", "house_plug", "house_plus", + "house_wifi", "ice_cream_bowl", "ice_cream_cone", "id_card", @@ -1577,6 +1626,7 @@ LUCIDE_ICON_LIST = [ "trending_up_down", "triangle", "triangle_alert", + "triangle_dashed", "triangle_right", "trophy", "truck", From 268effe62e203dc0eb495f50b14d0660891d942d Mon Sep 17 00:00:00 2001 From: Elijah Ahianyo Date: Mon, 20 Jan 2025 18:12:54 +0000 Subject: [PATCH 41/44] [ENG-4134]Allow specifying custom app module in rxconfig (#4556) * Allow custom app module in rxconfig * what was that pyscopg mess? * fix another mess * get this working with relative imports and hot reload * typing to named tuple * minor refactor * revert redis knobs positions * fix pyright except 1 * fix pyright hopefully * use the resolved module path * testing workflow * move nba-proxy job to counter job * just cast the type * fix tests for python 3.9 * darglint * CR Suggestions for #4556 (#4644) * reload_dirs: search up from app_module for last directory containing __init__ * Change custom app_module to use an import string * preserve sys.path entries added while loading rxconfig.py --------- Co-authored-by: Masen Furer --- .github/workflows/integration_tests.yml | 22 +++++++++++- reflex/app_module_for_backend.py | 7 ++-- reflex/config.py | 30 ++++++++++++++-- reflex/event.py | 2 +- reflex/state.py | 29 ++++++++-------- reflex/utils/exec.py | 26 ++++++++++++-- reflex/utils/prerequisites.py | 46 ++++++++++++++++++++++--- 7 files changed, 132 insertions(+), 30 deletions(-) diff --git a/.github/workflows/integration_tests.yml b/.github/workflows/integration_tests.yml index 017336ba5..2ca9aed23 100644 --- a/.github/workflows/integration_tests.yml +++ b/.github/workflows/integration_tests.yml @@ -33,7 +33,7 @@ env: PR_TITLE: ${{ github.event.pull_request.title }} jobs: - example-counter: + example-counter-and-nba-proxy: env: OUTPUT_FILE: import_benchmark.json timeout-minutes: 30 @@ -119,6 +119,26 @@ jobs: --benchmark-json "./reflex-examples/counter/${{ env.OUTPUT_FILE }}" --branch-name "${{ github.head_ref || github.ref_name }}" --pr-id "${{ github.event.pull_request.id }}" --app-name "counter" + - name: Install requirements for nba proxy example + working-directory: ./reflex-examples/nba-proxy + run: | + poetry run uv pip install -r requirements.txt + - name: Install additional dependencies for DB access + run: poetry run uv pip install psycopg + - name: Check export --backend-only before init for nba-proxy example + working-directory: ./reflex-examples/nba-proxy + run: | + poetry run reflex export --backend-only + - name: Init Website for nba-proxy example + working-directory: ./reflex-examples/nba-proxy + run: | + poetry run reflex init --loglevel debug + - name: Run Website and Check for errors + run: | + # Check that npm is home + npm -v + poetry run bash scripts/integration.sh ./reflex-examples/nba-proxy dev + reflex-web: strategy: diff --git a/reflex/app_module_for_backend.py b/reflex/app_module_for_backend.py index 8109fc3d6..b0ae0a29f 100644 --- a/reflex/app_module_for_backend.py +++ b/reflex/app_module_for_backend.py @@ -7,14 +7,13 @@ from concurrent.futures import ThreadPoolExecutor from reflex import constants from reflex.utils import telemetry from reflex.utils.exec import is_prod_mode -from reflex.utils.prerequisites import get_app +from reflex.utils.prerequisites import get_and_validate_app if constants.CompileVars.APP != "app": raise AssertionError("unexpected variable name for 'app'") telemetry.send("compile") -app_module = get_app(reload=False) -app = getattr(app_module, constants.CompileVars.APP) +app, app_module = get_and_validate_app(reload=False) # For py3.9 compatibility when redis is used, we MUST add any decorator pages # before compiling the app in a thread to avoid event loop error (REF-2172). app._apply_decorated_pages() @@ -30,7 +29,7 @@ if is_prod_mode(): # ensure only "app" is exposed. del app_module del compile_future -del get_app +del get_and_validate_app del is_prod_mode del telemetry del constants diff --git a/reflex/config.py b/reflex/config.py index 7614417d5..8511694fb 100644 --- a/reflex/config.py +++ b/reflex/config.py @@ -12,6 +12,7 @@ import threading import urllib.parse from importlib.util import find_spec from pathlib import Path +from types import ModuleType from typing import ( TYPE_CHECKING, Any, @@ -607,6 +608,9 @@ class Config(Base): # The name of the app (should match the name of the app directory). app_name: str + # The path to the app module. + app_module_import: Optional[str] = None + # The log level to use. loglevel: constants.LogLevel = constants.LogLevel.DEFAULT @@ -729,6 +733,19 @@ class Config(Base): "REDIS_URL is required when using the redis state manager." ) + @property + def app_module(self) -> ModuleType | None: + """Return the app module if `app_module_import` is set. + + Returns: + The app module. + """ + return ( + importlib.import_module(self.app_module_import) + if self.app_module_import + else None + ) + @property def module(self) -> str: """Get the module name of the app. @@ -736,6 +753,8 @@ class Config(Base): Returns: The module name. """ + if self.app_module is not None: + return self.app_module.__name__ return ".".join([self.app_name, self.app_name]) def update_from_env(self) -> dict[str, Any]: @@ -874,7 +893,7 @@ def get_config(reload: bool = False) -> Config: return cached_rxconfig.config with _config_lock: - sys_path = sys.path.copy() + orig_sys_path = sys.path.copy() sys.path.clear() sys.path.append(str(Path.cwd())) try: @@ -882,9 +901,14 @@ def get_config(reload: bool = False) -> Config: return _get_config() except Exception: # If the module import fails, try to import with the original sys.path. - sys.path.extend(sys_path) + sys.path.extend(orig_sys_path) return _get_config() finally: + # Find any entries added to sys.path by rxconfig.py itself. + extra_paths = [ + p for p in sys.path if p not in orig_sys_path and p != str(Path.cwd()) + ] # Restore the original sys.path. sys.path.clear() - sys.path.extend(sys_path) + sys.path.extend(extra_paths) + sys.path.extend(orig_sys_path) diff --git a/reflex/event.py b/reflex/event.py index 28852fde5..886a306c1 100644 --- a/reflex/event.py +++ b/reflex/event.py @@ -1591,7 +1591,7 @@ def get_handler_args( def fix_events( - events: list[EventHandler | EventSpec] | None, + events: list[EventSpec | EventHandler] | None, token: str, router_data: dict[str, Any] | None = None, ) -> list[Event]: diff --git a/reflex/state.py b/reflex/state.py index e15c73978..66098d232 100644 --- a/reflex/state.py +++ b/reflex/state.py @@ -1776,9 +1776,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): except Exception as ex: state._clean() - app_instance = getattr(prerequisites.get_app(), constants.CompileVars.APP) - - event_specs = app_instance.backend_exception_handler(ex) + event_specs = ( + prerequisites.get_and_validate_app().app.backend_exception_handler(ex) + ) if event_specs is None: return StateUpdate() @@ -1888,9 +1888,9 @@ class BaseState(Base, ABC, extra=pydantic.Extra.allow): except Exception as ex: telemetry.send_error(ex, context="backend") - app_instance = getattr(prerequisites.get_app(), constants.CompileVars.APP) - - event_specs = app_instance.backend_exception_handler(ex) + event_specs = ( + prerequisites.get_and_validate_app().app.backend_exception_handler(ex) + ) yield state._as_state_update( handler, @@ -2403,8 +2403,9 @@ class FrontendEventExceptionState(State): component_stack: The stack trace of the component where the exception occurred. """ - app_instance = getattr(prerequisites.get_app(), constants.CompileVars.APP) - app_instance.frontend_exception_handler(Exception(stack)) + prerequisites.get_and_validate_app().app.frontend_exception_handler( + Exception(stack) + ) class UpdateVarsInternalState(State): @@ -2442,15 +2443,16 @@ class OnLoadInternalState(State): The list of events to queue for on load handling. """ # Do not app._compile()! It should be already compiled by now. - app = getattr(prerequisites.get_app(), constants.CompileVars.APP) - load_events = app.get_load_events(self.router.page.path) + load_events = prerequisites.get_and_validate_app().app.get_load_events( + self.router.page.path + ) if not load_events: self.is_hydrated = True return # Fast path for navigation with no on_load events defined. self.is_hydrated = False return [ *fix_events( - load_events, + cast(list[Union[EventSpec, EventHandler]], load_events), self.router.session.client_token, router_data=self.router_data, ), @@ -2609,7 +2611,7 @@ class StateProxy(wrapt.ObjectProxy): """ super().__init__(state_instance) # compile is not relevant to backend logic - self._self_app = getattr(prerequisites.get_app(), constants.CompileVars.APP) + self._self_app = prerequisites.get_and_validate_app().app self._self_substate_path = tuple(state_instance.get_full_name().split(".")) self._self_actx = None self._self_mutable = False @@ -3702,8 +3704,7 @@ def get_state_manager() -> StateManager: Returns: The state manager. """ - app = getattr(prerequisites.get_app(), constants.CompileVars.APP) - return app.state_manager + return prerequisites.get_and_validate_app().app.state_manager class MutableProxy(wrapt.ObjectProxy): diff --git a/reflex/utils/exec.py b/reflex/utils/exec.py index 621c4a608..6087818d9 100644 --- a/reflex/utils/exec.py +++ b/reflex/utils/exec.py @@ -240,6 +240,28 @@ def run_backend( run_uvicorn_backend(host, port, loglevel) +def get_reload_dirs() -> list[str]: + """Get the reload directories for the backend. + + Returns: + The reload directories for the backend. + """ + config = get_config() + reload_dirs = [config.app_name] + if config.app_module is not None and config.app_module.__file__: + module_path = Path(config.app_module.__file__).resolve().parent + while module_path.parent.name: + for parent_file in module_path.parent.iterdir(): + if parent_file == "__init__.py": + # go up a level to find dir without `__init__.py` + module_path = module_path.parent + break + else: + break + reload_dirs.append(str(module_path)) + return reload_dirs + + def run_uvicorn_backend(host, port, loglevel: LogLevel): """Run the backend in development mode using Uvicorn. @@ -256,7 +278,7 @@ def run_uvicorn_backend(host, port, loglevel: LogLevel): port=port, log_level=loglevel.value, reload=True, - reload_dirs=[get_config().app_name], + reload_dirs=get_reload_dirs(), ) @@ -281,7 +303,7 @@ def run_granian_backend(host, port, loglevel: LogLevel): interface=Interfaces.ASGI, log_level=LogLevels(loglevel.value), reload=True, - reload_paths=[Path(get_config().app_name)], + reload_paths=get_reload_dirs(), reload_ignore_dirs=[".web"], ).serve() except ImportError: diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index e450393c3..ac1eb58da 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -17,11 +17,12 @@ import stat import sys import tempfile import time +import typing import zipfile from datetime import datetime from pathlib import Path from types import ModuleType -from typing import Callable, List, Optional +from typing import Callable, List, NamedTuple, Optional import httpx import typer @@ -42,9 +43,19 @@ from reflex.utils.exceptions import ( from reflex.utils.format import format_library_name from reflex.utils.registry import _get_npm_registry +if typing.TYPE_CHECKING: + from reflex.app import App + CURRENTLY_INSTALLING_NODE = False +class AppInfo(NamedTuple): + """A tuple containing the app instance and module.""" + + app: App + module: ModuleType + + @dataclasses.dataclass(frozen=True) class Template: """A template for a Reflex app.""" @@ -291,8 +302,11 @@ def get_app(reload: bool = False) -> ModuleType: ) module = config.module sys.path.insert(0, str(Path.cwd())) - app = __import__(module, fromlist=(constants.CompileVars.APP,)) - + app = ( + __import__(module, fromlist=(constants.CompileVars.APP,)) + if not config.app_module + else config.app_module + ) if reload: from reflex.state import reload_state_module @@ -308,6 +322,29 @@ def get_app(reload: bool = False) -> ModuleType: raise +def get_and_validate_app(reload: bool = False) -> AppInfo: + """Get the app instance based on the default config and validate it. + + Args: + reload: Re-import the app module from disk + + Returns: + The app instance and the app module. + + Raises: + RuntimeError: If the app instance is not an instance of rx.App. + """ + from reflex.app import App + + app_module = get_app(reload=reload) + app = getattr(app_module, constants.CompileVars.APP) + if not isinstance(app, App): + raise RuntimeError( + "The app instance in the specified app_module_import in rxconfig must be an instance of rx.App." + ) + return AppInfo(app=app, module=app_module) + + def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType: """Get the app module based on the default config after first compiling it. @@ -318,8 +355,7 @@ def get_compiled_app(reload: bool = False, export: bool = False) -> ModuleType: Returns: The compiled app based on the default config. """ - app_module = get_app(reload=reload) - app = getattr(app_module, constants.CompileVars.APP) + app, app_module = get_and_validate_app(reload=reload) # For py3.9 compatibility when redis is used, we MUST add any decorator pages # before compiling the app in a thread to avoid event loop error (REF-2172). app._apply_decorated_pages() From 9c019a65d588bbd9592e99bb6a0abce8145f741f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= Date: Mon, 20 Jan 2025 13:55:53 -0800 Subject: [PATCH 42/44] check for dict passed as children for component (#4656) --- reflex/components/component.py | 15 +++++++-------- reflex/utils/exceptions.py | 18 +++++++++++++++++- tests/units/components/test_component.py | 17 ++++++++++------- 3 files changed, 34 insertions(+), 16 deletions(-) diff --git a/reflex/components/component.py b/reflex/components/component.py index 8649b593d..cfe9a8dc2 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -740,22 +740,21 @@ class Component(BaseComponent, ABC): # Import here to avoid circular imports. from reflex.components.base.bare import Bare from reflex.components.base.fragment import Fragment - from reflex.utils.exceptions import ComponentTypeError + from reflex.utils.exceptions import ChildrenTypeError # Filter out None props props = {key: value for key, value in props.items() if value is not None} def validate_children(children): for child in children: - if isinstance(child, tuple): + if isinstance(child, (tuple, list)): validate_children(child) + # Make sure the child is a valid type. - if not types._isinstance(child, ComponentChild): - raise ComponentTypeError( - "Children of Reflex components must be other components, " - "state vars, or primitive Python types. " - f"Got child {child} of type {type(child)}.", - ) + if isinstance(child, dict) or not types._isinstance( + child, ComponentChild + ): + raise ChildrenTypeError(component=cls.__name__, child=child) # Validate all the children. validate_children(children) diff --git a/reflex/utils/exceptions.py b/reflex/utils/exceptions.py index 339abcda1..838d0a89d 100644 --- a/reflex/utils/exceptions.py +++ b/reflex/utils/exceptions.py @@ -1,6 +1,6 @@ """Custom Exceptions.""" -from typing import NoReturn +from typing import Any, NoReturn class ReflexError(Exception): @@ -31,6 +31,22 @@ class ComponentTypeError(ReflexError, TypeError): """Custom TypeError for component related errors.""" +class ChildrenTypeError(ComponentTypeError): + """Raised when the children prop of a component is not a valid type.""" + + def __init__(self, component: str, child: Any): + """Initialize the exception. + + Args: + component: The name of the component. + child: The child that caused the error. + """ + super().__init__( + f"Component {component} received child {child} of type {type(child)}. " + "Accepted types are other components, state vars, or primitive Python types (dict excluded)." + ) + + class EventHandlerTypeError(ReflexError, TypeError): """Custom TypeError for event handler related errors.""" diff --git a/tests/units/components/test_component.py b/tests/units/components/test_component.py index 674873b69..6396e4322 100644 --- a/tests/units/components/test_component.py +++ b/tests/units/components/test_component.py @@ -27,7 +27,7 @@ from reflex.event import ( from reflex.state import BaseState from reflex.style import Style from reflex.utils import imports -from reflex.utils.exceptions import EventFnArgMismatch +from reflex.utils.exceptions import ChildrenTypeError, EventFnArgMismatch from reflex.utils.imports import ImportDict, ImportVar, ParsedImportDict, parse_imports from reflex.vars import VarData from reflex.vars.base import LiteralVar, Var @@ -645,14 +645,17 @@ def test_create_filters_none_props(test_component): assert str(component.style["text-align"]) == '"center"' -@pytest.mark.parametrize("children", [((None,),), ("foo", ("bar", (None,)))]) +@pytest.mark.parametrize( + "children", + [ + ((None,),), + ("foo", ("bar", (None,))), + ({"foo": "bar"},), + ], +) def test_component_create_unallowed_types(children, test_component): - with pytest.raises(TypeError) as err: + with pytest.raises(ChildrenTypeError): test_component.create(*children) - assert ( - err.value.args[0] - == "Children of Reflex components must be other components, state vars, or primitive Python types. Got child None of type ." - ) @pytest.mark.parametrize( From 2855ed488719ff51492b8968c473f57345c5abee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Brand=C3=A9ho?= Date: Mon, 20 Jan 2025 13:58:17 -0800 Subject: [PATCH 43/44] add some of the TRY rules (#4651) --- pyproject.toml | 4 +- reflex/app.py | 8 +--- reflex/components/component.py | 20 ++++---- reflex/custom_components/custom_components.py | 8 ++-- reflex/utils/prerequisites.py | 48 +++++++++++++------ reflex/utils/telemetry.py | 3 +- tests/integration/test_connection_banner.py | 3 +- 7 files changed, 56 insertions(+), 38 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index eccf21230..d1ae1dcf0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -86,8 +86,8 @@ build-backend = "poetry.core.masonry.api" target-version = "py39" output-format = "concise" lint.isort.split-on-trailing-comma = false -lint.select = ["B", "C4", "D", "E", "ERA", "F", "FURB", "I", "PERF", "PTH", "RUF", "SIM", "T", "W"] -lint.ignore = ["B008", "D205", "E501", "F403", "SIM115", "RUF006", "RUF012"] +lint.select = ["B", "C4", "D", "E", "ERA", "F", "FURB", "I", "PERF", "PTH", "RUF", "SIM", "T", "TRY", "W"] +lint.ignore = ["B008", "D205", "E501", "F403", "SIM115", "RUF006", "RUF012", "TRY0"] lint.pydocstyle.convention = "google" [tool.ruff.lint.per-file-ignores] diff --git a/reflex/app.py b/reflex/app.py index 08cb4314e..60be0d7dd 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -463,14 +463,8 @@ class App(MiddlewareMixin, LifespanMixin): Returns: The generated component. - - Raises: - exceptions.MatchTypeError: If the return types of match cases in rx.match are different. """ - try: - return component if isinstance(component, Component) else component() - except exceptions.MatchTypeError: - raise + return component if isinstance(component, Component) else component() def add_page( self, diff --git a/reflex/components/component.py b/reflex/components/component.py index cfe9a8dc2..ed90a0f24 100644 --- a/reflex/components/component.py +++ b/reflex/components/component.py @@ -429,20 +429,22 @@ class Component(BaseComponent, ABC): else: continue + def determine_key(value): + # Try to create a var from the value + key = value if isinstance(value, Var) else LiteralVar.create(value) + + # Check that the var type is not None. + if key is None: + raise TypeError + + return key + # Check whether the key is a component prop. if types._issubclass(field_type, Var): # Used to store the passed types if var type is a union. passed_types = None try: - # Try to create a var from the value. - if isinstance(value, Var): - kwargs[key] = value - else: - kwargs[key] = LiteralVar.create(value) - - # Check that the var type is not None. - if kwargs[key] is None: - raise TypeError + kwargs[key] = determine_key(value) expected_type = fields[key].outer_type_.__args__[0] # validate literal fields. diff --git a/reflex/custom_components/custom_components.py b/reflex/custom_components/custom_components.py index 4a169802f..8000e7f4c 100644 --- a/reflex/custom_components/custom_components.py +++ b/reflex/custom_components/custom_components.py @@ -421,12 +421,13 @@ def _run_commands_in_subprocess(cmds: list[str]) -> bool: console.debug(f"Running command: {' '.join(cmds)}") try: result = subprocess.run(cmds, capture_output=True, text=True, check=True) - console.debug(result.stdout) - return True except subprocess.CalledProcessError as cpe: console.error(cpe.stdout) console.error(cpe.stderr) return False + else: + console.debug(result.stdout) + return True def _make_pyi_files(): @@ -931,10 +932,11 @@ def _get_file_from_prompt_in_loop() -> Tuple[bytes, str] | None: file_extension = image_filepath.suffix try: image_file = image_filepath.read_bytes() - return image_file, file_extension except OSError as ose: console.error(f"Unable to read the {file_extension} file due to {ose}") raise typer.Exit(code=1) from ose + else: + return image_file, file_extension console.debug(f"File extension detected: {file_extension}") return None diff --git a/reflex/utils/prerequisites.py b/reflex/utils/prerequisites.py index ac1eb58da..4f9cc0c14 100644 --- a/reflex/utils/prerequisites.py +++ b/reflex/utils/prerequisites.py @@ -278,6 +278,22 @@ def windows_npm_escape_hatch() -> bool: return environment.REFLEX_USE_NPM.get() +def _check_app_name(config: Config): + """Check if the app name is set in the config. + + Args: + config: The config object. + + Raises: + RuntimeError: If the app name is not set in the config. + """ + if not config.app_name: + raise RuntimeError( + "Cannot get the app module because `app_name` is not set in rxconfig! " + "If this error occurs in a reflex test case, ensure that `get_app` is mocked." + ) + + def get_app(reload: bool = False) -> ModuleType: """Get the app module based on the default config. @@ -288,18 +304,16 @@ def get_app(reload: bool = False) -> ModuleType: The app based on the default config. Raises: - RuntimeError: If the app name is not set in the config. + Exception: If an error occurs while getting the app module. """ from reflex.utils import telemetry try: environment.RELOAD_CONFIG.set(reload) config = get_config() - if not config.app_name: - raise RuntimeError( - "Cannot get the app module because `app_name` is not set in rxconfig! " - "If this error occurs in a reflex test case, ensure that `get_app` is mocked." - ) + + _check_app_name(config) + module = config.module sys.path.insert(0, str(Path.cwd())) app = ( @@ -315,11 +329,11 @@ def get_app(reload: bool = False) -> ModuleType: # Reload the app module. importlib.reload(app) - - return app except Exception as ex: telemetry.send_error(ex, context="frontend") raise + else: + return app def get_and_validate_app(reload: bool = False) -> AppInfo: @@ -1189,11 +1203,12 @@ def ensure_reflex_installation_id() -> Optional[int]: if installation_id is None: installation_id = random.getrandbits(128) installation_id_file.write_text(str(installation_id)) - # If we get here, installation_id is definitely set - return installation_id except Exception as e: console.debug(f"Failed to ensure reflex installation id: {e}") return None + else: + # If we get here, installation_id is definitely set + return installation_id def initialize_reflex_user_directory(): @@ -1407,19 +1422,22 @@ def create_config_init_app_from_remote_template(app_name: str, template_url: str except OSError as ose: console.error(f"Failed to create temp directory for extracting zip: {ose}") raise typer.Exit(1) from ose + try: zipfile.ZipFile(zip_file_path).extractall(path=unzip_dir) # The zip file downloaded from github looks like: # repo-name-branch/**/*, so we need to remove the top level directory. - if len(subdirs := os.listdir(unzip_dir)) != 1: - console.error(f"Expected one directory in the zip, found {subdirs}") - raise typer.Exit(1) - template_dir = unzip_dir / subdirs[0] - console.debug(f"Template folder is located at {template_dir}") except Exception as uze: console.error(f"Failed to unzip the template: {uze}") raise typer.Exit(1) from uze + if len(subdirs := os.listdir(unzip_dir)) != 1: + console.error(f"Expected one directory in the zip, found {subdirs}") + raise typer.Exit(1) + + template_dir = unzip_dir / subdirs[0] + console.debug(f"Template folder is located at {template_dir}") + # Move the rxconfig file here first. path_ops.mv(str(template_dir / constants.Config.FILE), constants.Config.FILE) new_config = get_config(reload=True) diff --git a/reflex/utils/telemetry.py b/reflex/utils/telemetry.py index fc90932a6..8e9130b09 100644 --- a/reflex/utils/telemetry.py +++ b/reflex/utils/telemetry.py @@ -156,9 +156,10 @@ def _prepare_event(event: str, **kwargs) -> dict: def _send_event(event_data: dict) -> bool: try: httpx.post(POSTHOG_API_URL, json=event_data) - return True except Exception: return False + else: + return True def _send(event, telemetry_enabled, **kwargs): diff --git a/tests/integration/test_connection_banner.py b/tests/integration/test_connection_banner.py index 44187c8ba..18259fe3f 100644 --- a/tests/integration/test_connection_banner.py +++ b/tests/integration/test_connection_banner.py @@ -71,9 +71,10 @@ def has_error_modal(driver: WebDriver) -> bool: """ try: driver.find_element(By.XPATH, CONNECTION_ERROR_XPATH) - return True except NoSuchElementException: return False + else: + return True @pytest.mark.asyncio From 4dc106545b1f42535bf278ca4092a878515b614d Mon Sep 17 00:00:00 2001 From: Khaleel Al-Adhami Date: Mon, 20 Jan 2025 14:00:08 -0800 Subject: [PATCH 44/44] add defensive checks against data being funny (#4633) --- reflex/app.py | 30 ++++++++++++++++++++++++++++-- reflex/utils/exceptions.py | 4 ++++ 2 files changed, 32 insertions(+), 2 deletions(-) diff --git a/reflex/app.py b/reflex/app.py index 60be0d7dd..0d672e4c0 100644 --- a/reflex/app.py +++ b/reflex/app.py @@ -1557,10 +1557,36 @@ class EventNamespace(AsyncNamespace): Args: sid: The Socket.IO session id. data: The event data. + + Raises: + EventDeserializationError: If the event data is not a dictionary. """ fields = data - # Get the event. - event = Event(**{k: v for k, v in fields.items() if k in _EVENT_FIELDS}) + + if isinstance(fields, str): + console.warn( + "Received event data as a string. This generally should not happen and may indicate a bug." + f" Event data: {fields}" + ) + try: + fields = json.loads(fields) + except json.JSONDecodeError as ex: + raise exceptions.EventDeserializationError( + f"Failed to deserialize event data: {fields}." + ) from ex + + if not isinstance(fields, dict): + raise exceptions.EventDeserializationError( + f"Event data must be a dictionary, but received {fields} of type {type(fields)}." + ) + + try: + # Get the event. + event = Event(**{k: v for k, v in fields.items() if k in _EVENT_FIELDS}) + except (TypeError, ValueError) as ex: + raise exceptions.EventDeserializationError( + f"Failed to deserialize event data: {fields}." + ) from ex self.token_to_sid[event.token] = sid self.sid_to_token[sid] = event.token diff --git a/reflex/utils/exceptions.py b/reflex/utils/exceptions.py index 838d0a89d..37a68e420 100644 --- a/reflex/utils/exceptions.py +++ b/reflex/utils/exceptions.py @@ -187,6 +187,10 @@ class SystemPackageMissingError(ReflexError): """Raised when a system package is missing.""" +class EventDeserializationError(ReflexError, ValueError): + """Raised when an event cannot be deserialized.""" + + def raise_system_package_missing_error(package: str) -> NoReturn: """Raise a SystemPackageMissingError.