Skip to content

Commit

Permalink
Remove Pydantic from some classes (#3907)
Browse files Browse the repository at this point in the history
* half of the way there

* add dataclass support

* Forbid Computed var shadowing (#3843)

* get it right pyright

* fix unit tests

* rip out more pydantic

* fix weird issues with merge_imports

* add missing docstring

* make special props a list instead of a set

* fix moment pyi

* actually ignore the runtime error

* it's ruff out there

---------

Co-authored-by: benedikt-bartscher <31854409+benedikt-bartscher@users.noreply.github.com>
  • Loading branch information
adhami3310 and benedikt-bartscher authored Sep 13, 2024
1 parent 7c25358 commit 8f937f0
Show file tree
Hide file tree
Showing 27 changed files with 395 additions and 249 deletions.
5 changes: 4 additions & 1 deletion reflex/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import functools
import inspect
import io
import json
import multiprocessing
import os
import platform
Expand Down Expand Up @@ -1096,6 +1097,7 @@ async def modify_state(self, token: str) -> AsyncIterator[BaseState]:
if delta:
# When the state is modified reset dirty status and emit the delta to the frontend.
state._clean()
print(dir(state.router))
await self.event_namespace.emit_update(
update=StateUpdate(delta=delta),
sid=state.router.session.session_id,
Expand Down Expand Up @@ -1531,8 +1533,9 @@ async def on_event(self, sid, data):
sid: The Socket.IO session id.
data: The event data.
"""
fields = json.loads(data)
# Get the event.
event = Event.parse_raw(data)
event = Event(**{k: v for k, v in fields.items() if k != "handler"})

self.token_to_sid[event.token] = sid
self.sid_to_token[sid] = event.token
Expand Down
9 changes: 6 additions & 3 deletions reflex/components/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import copy
import typing
import warnings
from abc import ABC, abstractmethod
from functools import lru_cache, wraps
from hashlib import md5
Expand Down Expand Up @@ -169,6 +170,8 @@ def evaluate_style_namespaces(style: ComponentStyle) -> dict:
]
ComponentChild = Union[types.PrimitiveType, Var, BaseComponent]

warnings.filterwarnings("ignore", message="fields may not start with an underscore")


class Component(BaseComponent, ABC):
"""A component with style, event trigger and other props."""
Expand All @@ -195,7 +198,7 @@ class Component(BaseComponent, ABC):
class_name: Any = None

# Special component props.
special_props: Set[ImmutableVar] = set()
special_props: List[ImmutableVar] = []

# Whether the component should take the focus once the page is loaded
autofocus: bool = False
Expand Down Expand Up @@ -655,7 +658,7 @@ def _render(self, props: dict[str, Any] | None = None) -> Tag:
"""
# Create the base tag.
tag = Tag(
name=self.tag if not self.alias else self.alias,
name=(self.tag if not self.alias else self.alias) or "",
special_props=self.special_props,
)

Expand Down Expand Up @@ -2244,7 +2247,7 @@ def render(self) -> dict:
Returns:
The tag to render.
"""
return dict(Tag(name=self.tag))
return dict(Tag(name=self.tag or ""))

def __str__(self) -> str:
"""Represent the component in React.
Expand Down
8 changes: 4 additions & 4 deletions reflex/components/core/upload.py
Original file line number Diff line number Diff line change
Expand Up @@ -247,19 +247,19 @@ def create(cls, *children, **props) -> Component:
}
# The file input to use.
upload = Input.create(type="file")
upload.special_props = {
upload.special_props = [
ImmutableVar(_var_name="{...getInputProps()}", _var_type=None)
}
]

# The dropzone to use.
zone = Box.create(
upload,
*children,
**{k: v for k, v in props.items() if k not in supported_props},
)
zone.special_props = {
zone.special_props = [
ImmutableVar(_var_name="{...getRootProps()}", _var_type=None)
}
]

# Create the component.
upload_props["id"] = props.get("id", DEFAULT_UPLOAD_ID)
Expand Down
6 changes: 3 additions & 3 deletions reflex/components/el/elements/metadata.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"""Element classes. This is an auto-generated file. Do not edit. See ../generate.py."""

from typing import Set, Union
from typing import List, Union

from reflex.components.el.element import Element
from reflex.ivars.base import ImmutableVar
Expand Down Expand Up @@ -90,9 +90,9 @@ class StyleEl(Element): # noqa: E742

media: Var[Union[str, int, bool]]

special_props: Set[ImmutableVar] = {
special_props: List[ImmutableVar] = [
ImmutableVar.create_safe("suppressHydrationWarning")
}
]


base = Base.create
Expand Down
6 changes: 3 additions & 3 deletions reflex/components/markdown/markdown.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,17 +195,17 @@ def get_component(self, tag: str, **props) -> Component:
if tag not in self.component_map:
raise ValueError(f"No markdown component found for tag: {tag}.")

special_props = {_PROPS_IN_TAG}
special_props = [_PROPS_IN_TAG]
children = [_CHILDREN]

# For certain tags, the props from the markdown renderer are not actually valid for the component.
if tag in NO_PROPS_TAGS:
special_props = set()
special_props = []

# If the children are set as a prop, don't pass them as children.
children_prop = props.pop("children", None)
if children_prop is not None:
special_props.add(
special_props.append(
ImmutableVar.create_safe(f"children={{{str(children_prop)}}}")
)
children = []
Expand Down
23 changes: 12 additions & 11 deletions reflex/components/moment/moment.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,27 @@
"""Moment component for humanized date rendering."""

import dataclasses
from typing import List, Optional

from reflex.base import Base
from reflex.components.component import Component, NoSSRComponent
from reflex.event import EventHandler
from reflex.utils.imports import ImportDict
from reflex.vars import Var


class MomentDelta(Base):
@dataclasses.dataclass(frozen=True)
class MomentDelta:
"""A delta used for add/subtract prop in Moment."""

years: Optional[int]
quarters: Optional[int]
months: Optional[int]
weeks: Optional[int]
days: Optional[int]
hours: Optional[int]
minutess: Optional[int]
seconds: Optional[int]
milliseconds: Optional[int]
years: Optional[int] = dataclasses.field(default=None)
quarters: Optional[int] = dataclasses.field(default=None)
months: Optional[int] = dataclasses.field(default=None)
weeks: Optional[int] = dataclasses.field(default=None)
days: Optional[int] = dataclasses.field(default=None)
hours: Optional[int] = dataclasses.field(default=None)
minutess: Optional[int] = dataclasses.field(default=None)
seconds: Optional[int] = dataclasses.field(default=None)
milliseconds: Optional[int] = dataclasses.field(default=None)


class Moment(NoSSRComponent):
Expand Down
5 changes: 3 additions & 2 deletions reflex/components/moment/moment.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@
# ------------------- DO NOT EDIT ----------------------
# This file was generated by `reflex/utils/pyi_generator.py`!
# ------------------------------------------------------
import dataclasses
from typing import Any, Callable, Dict, Optional, Union, overload

from reflex.base import Base
from reflex.components.component import NoSSRComponent
from reflex.event import EventHandler, EventSpec
from reflex.ivars.base import ImmutableVar
from reflex.style import Style
from reflex.utils.imports import ImportDict
from reflex.vars import Var

class MomentDelta(Base):
@dataclasses.dataclass(frozen=True)
class MomentDelta:
years: Optional[int]
quarters: Optional[int]
months: Optional[int]
Expand Down
4 changes: 2 additions & 2 deletions reflex/components/plotly/plotly.py
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ def _render(self):
template_dict = LiteralVar.create({"layout": {"template": self.template}})
merge_dicts.append(template_dict.without_data())
if merge_dicts:
tag.special_props.add(
tag.special_props.append(
# Merge all dictionaries and spread the result over props.
ImmutableVar.create_safe(
f"{{...mergician({str(figure)},"
Expand All @@ -276,5 +276,5 @@ def _render(self):
)
else:
# Spread the figure dict over props, nothing to merge.
tag.special_props.add(ImmutableVar.create_safe(f"{{...{str(figure)}}}"))
tag.special_props.append(ImmutableVar.create_safe(f"{{...{str(figure)}}}"))
return tag
9 changes: 6 additions & 3 deletions reflex/components/tags/cond_tag.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
"""Tag to conditionally render components."""

import dataclasses
from typing import Any, Dict, Optional

from reflex.components.tags.tag import Tag
from reflex.ivars.base import LiteralVar
from reflex.vars import Var


@dataclasses.dataclass()
class CondTag(Tag):
"""A conditional tag."""

# The condition to determine which component to render.
cond: Var[Any]
cond: Var[Any] = dataclasses.field(default_factory=lambda: LiteralVar.create(True))

# The code to render if the condition is true.
true_value: Dict
true_value: Dict = dataclasses.field(default_factory=dict)

# The code to render if the condition is false.
false_value: Optional[Dict]
false_value: Optional[Dict] = None
15 changes: 10 additions & 5 deletions reflex/components/tags/iter_tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,36 @@

from __future__ import annotations

import dataclasses
import inspect
from typing import TYPE_CHECKING, Any, Callable, List, Tuple, Type, Union, get_args

from reflex.components.tags.tag import Tag
from reflex.ivars.base import ImmutableVar
from reflex.vars import Var
from reflex.ivars.sequence import LiteralArrayVar
from reflex.vars import Var, get_unique_variable_name

if TYPE_CHECKING:
from reflex.components.component import Component


@dataclasses.dataclass()
class IterTag(Tag):
"""An iterator tag."""

# The var to iterate over.
iterable: Var[List]
iterable: Var[List] = dataclasses.field(
default_factory=lambda: LiteralArrayVar.create([])
)

# The component render function for each item in the iterable.
render_fn: Callable
render_fn: Callable = dataclasses.field(default_factory=lambda: lambda x: x)

# The name of the arg var.
arg_var_name: str
arg_var_name: str = dataclasses.field(default_factory=get_unique_variable_name)

# The name of the index var.
index_var_name: str
index_var_name: str = dataclasses.field(default_factory=get_unique_variable_name)

def get_iterable_var_type(self) -> Type:
"""Get the type of the iterable var.
Expand Down
9 changes: 6 additions & 3 deletions reflex/components/tags/match_tag.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
"""Tag to conditionally match cases."""

import dataclasses
from typing import Any, List

from reflex.components.tags.tag import Tag
from reflex.ivars.base import LiteralVar
from reflex.vars import Var


@dataclasses.dataclass()
class MatchTag(Tag):
"""A match tag."""

# The condition to determine which case to match.
cond: Var[Any]
cond: Var[Any] = dataclasses.field(default_factory=lambda: LiteralVar.create(True))

# The list of match cases to be matched.
match_cases: List[Any]
match_cases: List[Any] = dataclasses.field(default_factory=list)

# The catchall case to match.
default: Any
default: Any = dataclasses.field(default=LiteralVar.create(None))
59 changes: 38 additions & 21 deletions reflex/components/tags/tag.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,23 @@

from __future__ import annotations

from typing import Any, Dict, List, Optional, Set, Tuple, Union
import dataclasses
from typing import Any, Dict, List, Optional, Tuple, Union

from reflex.base import Base
from reflex.event import EventChain
from reflex.ivars.base import ImmutableVar, LiteralVar
from reflex.utils import format, types


class Tag(Base):
@dataclasses.dataclass()
class Tag:
"""A React tag."""

# The name of the tag.
name: str = ""

# The props of the tag.
props: Dict[str, Any] = {}
props: Dict[str, Any] = dataclasses.field(default_factory=dict)

# The inner contents of the tag.
contents: str = ""
Expand All @@ -26,25 +27,18 @@ class Tag(Base):
args: Optional[Tuple[str, ...]] = None

# Special props that aren't key value pairs.
special_props: Set[ImmutableVar] = set()
special_props: List[ImmutableVar] = dataclasses.field(default_factory=list)

# The children components.
children: List[Any] = []

def __init__(self, *args, **kwargs):
"""Initialize the tag.
Args:
*args: Args to initialize the tag.
**kwargs: Kwargs to initialize the tag.
"""
# Convert any props to vars.
if "props" in kwargs:
kwargs["props"] = {
name: LiteralVar.create(value)
for name, value in kwargs["props"].items()
}
super().__init__(*args, **kwargs)
children: List[Any] = dataclasses.field(default_factory=list)

def __post_init__(self):
"""Post initialize the tag."""
object.__setattr__(
self,
"props",
{name: LiteralVar.create(value) for name, value in self.props.items()},
)

def format_props(self) -> List:
"""Format the tag's props.
Expand All @@ -54,6 +48,29 @@ def format_props(self) -> List:
"""
return format.format_props(*self.special_props, **self.props)

def set(self, **kwargs: Any):
"""Set the tag's fields.
Args:
kwargs: The fields to set.
Returns:
The tag with the fields
"""
for name, value in kwargs.items():
setattr(self, name, value)

return self

def __iter__(self):
"""Iterate over the tag's fields.
Yields:
Tuple[str, Any]: The field name and value.
"""
for field in dataclasses.fields(self):
yield field.name, getattr(self, field.name)

def add_props(self, **kwargs: Optional[Any]) -> Tag:
"""Add props to the tag.
Expand Down
Loading

0 comments on commit 8f937f0

Please sign in to comment.