Skip to content

Commit c08d698

Browse files
authored
deprecate Component __init__ (#4904)
* deprecate Component __init__ * add hot loop for _unsafe_create * maybe ? * precommit * class vars all the way and improve style * we can't have memoization mode * optimize var checks inside of components * use default factory ?? * is functools faster than lambda? * handle private annotated assignments
1 parent 3a6f747 commit c08d698

29 files changed

+252
-196
lines changed

reflex/components/base/bare.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,13 @@ def create(cls, contents: Any) -> Component:
7070
if isinstance(contents, Var):
7171
if isinstance(contents, LiteralStringVar):
7272
validate_str(contents._var_value)
73-
return cls(contents=contents)
73+
return cls._unsafe_create(children=[], contents=contents)
7474
else:
7575
if isinstance(contents, str):
7676
validate_str(contents)
77-
contents = str(contents) if contents is not None else ""
77+
contents = Var.create(contents if contents is not None else "")
7878

79-
return cls._create(children=[], contents=contents)
79+
return cls._unsafe_create(children=[], contents=contents)
8080

8181
def _get_all_hooks_internal(self) -> dict[str, VarData | None]:
8282
"""Include the hooks for the component.

reflex/components/component.py

+86-42
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import copy
66
import dataclasses
7+
import functools
78
import inspect
89
import typing
910
from abc import ABC, abstractmethod
@@ -25,6 +26,9 @@
2526
get_origin,
2627
)
2728

29+
import pydantic.v1
30+
import pydantic.v1.fields
31+
2832
import reflex.state
2933
from reflex.base import Base
3034
from reflex.compiler.templates import STATEFUL_COMPONENT
@@ -73,19 +77,19 @@ class BaseComponent(Base, ABC):
7377
"""
7478

7579
# The children nested within the component.
76-
children: list[BaseComponent] = []
80+
children: list[BaseComponent] = pydantic.v1.Field(default_factory=list)
7781

7882
# The library that the component is based on.
79-
library: str | None = None
83+
library: str | None = pydantic.v1.Field(default_factory=lambda: None)
8084

8185
# List here the non-react dependency needed by `library`
82-
lib_dependencies: list[str] = []
86+
lib_dependencies: list[str] = pydantic.v1.Field(default_factory=list)
8387

8488
# List here the dependencies that need to be transpiled by Next.js
85-
transpile_packages: list[str] = []
89+
transpile_packages: list[str] = pydantic.v1.Field(default_factory=list)
8690

8791
# The tag to use when rendering the component.
88-
tag: str | None = None
92+
tag: str | None = pydantic.v1.Field(default_factory=lambda: None)
8993

9094
@abstractmethod
9195
def render(self) -> dict:
@@ -262,52 +266,56 @@ class Component(BaseComponent, ABC):
262266
"""A component with style, event trigger and other props."""
263267

264268
# The style of the component.
265-
style: Style = Style()
269+
style: Style = pydantic.v1.Field(default_factory=Style)
266270

267271
# A mapping from event triggers to event chains.
268-
event_triggers: dict[str, EventChain | Var] = {}
272+
event_triggers: dict[str, EventChain | Var] = pydantic.v1.Field(
273+
default_factory=dict
274+
)
269275

270276
# The alias for the tag.
271-
alias: str | None = None
277+
alias: str | None = pydantic.v1.Field(default_factory=lambda: None)
272278

273279
# Whether the import is default or named.
274-
is_default: bool | None = False
280+
is_default: bool | None = pydantic.v1.Field(default_factory=lambda: False)
275281

276282
# A unique key for the component.
277-
key: Any = None
283+
key: Any = pydantic.v1.Field(default_factory=lambda: None)
278284

279285
# The id for the component.
280-
id: Any = None
286+
id: Any = pydantic.v1.Field(default_factory=lambda: None)
281287

282288
# The class name for the component.
283-
class_name: Any = None
289+
class_name: Any = pydantic.v1.Field(default_factory=lambda: None)
284290

285291
# Special component props.
286-
special_props: list[Var] = []
292+
special_props: list[Var] = pydantic.v1.Field(default_factory=list)
287293

288294
# Whether the component should take the focus once the page is loaded
289-
autofocus: bool = False
295+
autofocus: bool = pydantic.v1.Field(default_factory=lambda: False)
290296

291297
# components that cannot be children
292-
_invalid_children: list[str] = []
298+
_invalid_children: ClassVar[list[str]] = []
293299

294300
# only components that are allowed as children
295-
_valid_children: list[str] = []
301+
_valid_children: ClassVar[list[str]] = []
296302

297303
# only components that are allowed as parent
298-
_valid_parents: list[str] = []
304+
_valid_parents: ClassVar[list[str]] = []
299305

300306
# props to change the name of
301-
_rename_props: dict[str, str] = {}
307+
_rename_props: ClassVar[dict[str, str]] = {}
302308

303309
# custom attribute
304-
custom_attrs: dict[str, Var | Any] = {}
310+
custom_attrs: dict[str, Var | Any] = pydantic.v1.Field(default_factory=dict)
305311

306312
# When to memoize this component and its children.
307313
_memoization_mode: MemoizationMode = MemoizationMode()
308314

309315
# State class associated with this component instance
310-
State: Type[reflex.state.State] | None = None
316+
State: Type[reflex.state.State] | None = pydantic.v1.Field(
317+
default_factory=lambda: None
318+
)
311319

312320
def add_imports(self) -> ImportDict | list[ImportDict]:
313321
"""Add imports for the component.
@@ -412,16 +420,14 @@ def __init_subclass__(cls, **kwargs):
412420
if field.name not in props:
413421
continue
414422

415-
field_type = types.value_inside_optional(
416-
types.get_field_type(cls, field.name)
417-
)
418-
419423
# Set default values for any props.
420-
if types._issubclass(field_type, Var):
424+
if field.type_ is Var:
421425
field.required = False
422426
if field.default is not None:
423-
field.default = LiteralVar.create(field.default)
424-
elif types._issubclass(field_type, EventHandler):
427+
field.default_factory = functools.partial(
428+
LiteralVar.create, field.default
429+
)
430+
elif field.type_ is EventHandler:
425431
field.required = False
426432

427433
# Ensure renamed props from parent classes are applied to the subclass.
@@ -432,6 +438,23 @@ def __init_subclass__(cls, **kwargs):
432438
inherited_rename_props.update(parent._rename_props)
433439
cls._rename_props = inherited_rename_props
434440

441+
def __init__(self, **kwargs):
442+
"""Initialize the custom component.
443+
444+
Args:
445+
**kwargs: The kwargs to pass to the component.
446+
"""
447+
console.deprecate(
448+
"component-direct-instantiation",
449+
reason="Use the `create` method instead.",
450+
deprecation_version="0.7.2",
451+
removal_version="0.8.0",
452+
)
453+
super().__init__(
454+
children=kwargs.get("children", []),
455+
)
456+
self._post_init(**kwargs)
457+
435458
def _post_init(self, *args, **kwargs):
436459
"""Initialize the component.
437460
@@ -472,13 +495,10 @@ def _post_init(self, *args, **kwargs):
472495
)
473496
if key in component_specific_triggers:
474497
# Event triggers are bound to event chains.
475-
field_type = EventChain
498+
is_var = False
476499
elif key in props:
477500
# Set the field type.
478-
field_type = types.value_inside_optional(
479-
types.get_field_type(type(self), key)
480-
)
481-
501+
is_var = field.type_ is Var if (field := fields.get(key)) else False
482502
else:
483503
continue
484504

@@ -493,7 +513,7 @@ def determine_key(value: Any):
493513
return key
494514

495515
# Check whether the key is a component prop.
496-
if types._issubclass(field_type, Var):
516+
if is_var:
497517
try:
498518
kwargs[key] = determine_key(value)
499519

@@ -565,9 +585,15 @@ def determine_key(value: Any):
565585
"&": style,
566586
}
567587

588+
fields_style = self.get_fields()["style"]
589+
568590
kwargs["style"] = Style(
569591
{
570-
**self.get_fields()["style"].default,
592+
**(
593+
fields_style.default_factory()
594+
if fields_style.default_factory
595+
else fields_style.default
596+
),
571597
**style,
572598
**{attr: value for attr, value in kwargs.items() if attr not in fields},
573599
}
@@ -779,7 +805,7 @@ def validate_children(children: tuple | list):
779805
# Validate all the children.
780806
validate_children(children)
781807

782-
children = [
808+
children_normalized = [
783809
(
784810
child
785811
if isinstance(child, Component)
@@ -792,10 +818,10 @@ def validate_children(children: tuple | list):
792818
for child in children
793819
]
794820

795-
return cls._create(children, **props)
821+
return cls._create(children_normalized, **props)
796822

797823
@classmethod
798-
def _create(cls: Type[T], children: list[Component], **props: Any) -> T:
824+
def _create(cls: Type[T], children: Sequence[BaseComponent], **props: Any) -> T:
799825
"""Create the component.
800826
801827
Args:
@@ -805,8 +831,26 @@ def _create(cls: Type[T], children: list[Component], **props: Any) -> T:
805831
Returns:
806832
The component.
807833
"""
808-
comp = cls.construct(id=props.get("id"), children=children)
809-
comp._post_init(children=children, **props)
834+
comp = cls.construct(id=props.get("id"), children=list(children))
835+
comp._post_init(children=list(children), **props)
836+
return comp
837+
838+
@classmethod
839+
def _unsafe_create(
840+
cls: Type[T], children: Sequence[BaseComponent], **props: Any
841+
) -> T:
842+
"""Create the component without running post_init.
843+
844+
Args:
845+
children: The children of the component.
846+
**props: The props of the component.
847+
848+
Returns:
849+
The component.
850+
"""
851+
comp = cls.construct(id=props.get("id"), children=list(children))
852+
for prop, value in props.items():
853+
setattr(comp, prop, value)
810854
return comp
811855

812856
def add_style(self) -> dict[str, Any] | None:
@@ -991,8 +1035,8 @@ def validate_child(child: Any):
9911035
validate_child(c)
9921036

9931037
if isinstance(child, Cond):
994-
validate_child(child.comp1)
995-
validate_child(child.comp2)
1038+
validate_child(child.children[0])
1039+
validate_child(child.children[1])
9961040

9971041
if isinstance(child, Match):
9981042
for cases in child.match_cases:
@@ -1769,7 +1813,7 @@ def get_args_spec(key: str) -> types.ArgsSpec | Sequence[types.ArgsSpec]:
17691813
type_ = props_types[key]
17701814

17711815
# Handle event chains.
1772-
if types._issubclass(type_, EventActionsMixin):
1816+
if type_ is EventHandler:
17731817
inspect.getfullargspec(component_fn).annotations[key]
17741818
self.props[camel_cased_key] = EventChain.create(
17751819
value=value, args_spec=get_args_spec(key), key=key

reflex/components/core/cond.py

+7-16
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,6 @@ class Cond(MemoizationLeaf):
2626
# The cond to determine which component to render.
2727
cond: Var[Any]
2828

29-
# The component to render if the cond is true.
30-
comp1: BaseComponent | None = None
31-
# The component to render if the cond is false.
32-
comp2: BaseComponent | None = None
33-
3429
@classmethod
3530
def create(
3631
cls,
@@ -54,19 +49,17 @@ def create(
5449
if comp2 is None or type(comp2).__name__ != "Fragment":
5550
comp2 = Fragment.create(comp2) if comp2 else Fragment.create()
5651
return Fragment.create(
57-
cls(
58-
cond=cond,
59-
comp1=comp1,
60-
comp2=comp2,
52+
cls._create(
6153
children=[comp1, comp2],
54+
cond=cond,
6255
)
6356
)
6457

6558
def _render(self) -> Tag:
6659
return CondTag(
6760
cond=self.cond,
68-
true_value=self.comp1.render(), # pyright: ignore [reportOptionalMemberAccess]
69-
false_value=self.comp2.render(), # pyright: ignore [reportOptionalMemberAccess]
61+
true_value=self.children[0].render(),
62+
false_value=self.children[1].render(),
7063
)
7164

7265
def render(self) -> Dict:
@@ -86,7 +79,7 @@ def render(self) -> Dict:
8679
).set(
8780
props=tag.format_props(),
8881
),
89-
cond_state=f"isTrue({self.cond!s})",
82+
cond_state=str(self.cond),
9083
)
9184

9285
def add_imports(self) -> ImportDict:
@@ -137,7 +130,7 @@ def cond(condition: Any, c1: Any, c2: Any = None) -> Component | Var:
137130
if isinstance(c1, BaseComponent):
138131
if c2 is not None and not isinstance(c2, BaseComponent):
139132
raise ValueError("Both arguments must be components.")
140-
return Cond.create(cond_var, c1, c2)
133+
return Cond.create(cond_var.bool(), c1, c2)
141134

142135
# Otherwise, create a conditional Var.
143136
# Check that the second argument is valid.
@@ -155,9 +148,7 @@ def cond(condition: Any, c1: Any, c2: Any = None) -> Component | Var:
155148

156149
# Create the conditional var.
157150
return ternary_operation(
158-
cond_var.bool()._replace(
159-
merge_var_data=VarData(imports=_IS_TRUE_IMPORT),
160-
),
151+
cond_var.bool(),
161152
c1_var,
162153
c2_var,
163154
)

reflex/components/core/debounce.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ def create(cls, *children: Component, **props: Any) -> Component:
127127
component._get_style = child._get_style
128128
component.event_triggers.update(child.event_triggers)
129129
component.children = child.children
130-
component._rename_props = child._rename_props
130+
component._rename_props = child._rename_props # pyright: ignore[reportAttributeAccessIssue]
131131
outer_get_all_custom_code = component._get_all_custom_code
132132
component._get_all_custom_code = lambda: outer_get_all_custom_code().union(
133133
child._get_all_custom_code()

reflex/components/core/foreach.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ def create(
9090
if types.is_optional(iterable._var_type):
9191
iterable = cond(iterable, iterable, [])
9292

93-
component = cls(
93+
component = cls._create(
94+
children=[],
9495
iterable=iterable,
9596
render_fn=render_fn,
9697
)

0 commit comments

Comments
 (0)