diff --git a/pynecone/components/__init__.py b/pynecone/components/__init__.py index b20e71462..b3c0f15d0 100644 --- a/pynecone/components/__init__.py +++ b/pynecone/components/__init__.py @@ -1,7 +1,9 @@ """Import all the components.""" +from __future__ import annotations + +from typing import TYPE_CHECKING from pynecone import utils -from pynecone.propcond import PropCond from .component import Component from .datadisplay import * @@ -15,6 +17,10 @@ from .navigation import * from .overlay import * from .typography import * +if TYPE_CHECKING: + from typing import Any + from pynecone.var import Var + # Add the convenience methods for all the components. locals().update( { @@ -91,17 +97,32 @@ def mobile_and_tablet(*children, **props): return Box.create(*children, **props, display=["block", "block", "block", "none"]) -def cond(cond_var, c1, c2=None): +def cond(condition: Any, c1: Any, c2: Any = None): """Create a conditional component or Prop. Args: - cond_var: The cond to determine which component to render. + condition: The cond to determine which component to render. c1: The component or prop to render if the cond_var is true. c2: The component or prop to render if the cond_var is false. Returns: The conditional component. """ - if isinstance(c1, Component) and isinstance(c2, Component): + # Import here to avoid circular imports. + from .tags.tag import PropCond + from pynecone.var import Var + + # Convert the condition to a Var. + cond_var = Var.create(condition) + assert cond_var is not None, "The condition must be set." + + # If the first component is a component, create a Cond component. + if isinstance(c1, Component): + assert c2 is None or isinstance( + c2, Component + ), "Both arguments must be components." return Cond.create(cond_var, c1, c2) + + # Otherwise, create a PropCond. + assert not isinstance(c2, Component), "Both arguments must be props." return PropCond.create(cond_var, c1, c2) diff --git a/pynecone/components/tags/tag.py b/pynecone/components/tags/tag.py index 513cef436..e20a62b49 100644 --- a/pynecone/components/tags/tag.py +++ b/pynecone/components/tags/tag.py @@ -12,7 +12,6 @@ from plotly.io import to_json from pynecone import utils from pynecone.base import Base -from pynecone.propcond import PropCond from pynecone.event import EventChain from pynecone.var import Var @@ -191,3 +190,54 @@ class Tag(Base): Whether the prop is valid. """ return prop is not None and not (isinstance(prop, dict) and len(prop) == 0) + + +class PropCond(Base): + """A conditional prop.""" + + # The condition to determine which prop to render. + cond: Var[Any] + + # The prop to render if the condition is true. + prop1: Any + + # The prop to render if the condition is false. + prop2: Any + + @classmethod + def create(cls, cond: Var, prop1: Any, prop2: Any): + """Create a conditional Prop. + + Args: + cond: The cond to determine which prop to render. + prop1: The prop value to render if the cond is true. + prop2: The prop value to render if the cond is false. + + Returns: + The conditional Prop. + + Raises: + ValueError: If the condition or prop values are not set. + """ + if cond is None: + raise ValueError("The condition must be set.") + if prop1 is None or prop2 is None: + raise ValueError("Both prop values must be set.") + return cls( + cond=cond, + prop1=prop1, + prop2=prop2, + ) + + def __str__(self) -> str: + """Render the prop as a React string. + + Returns: + The React code to render the prop. + """ + return utils.format_cond( + cond=self.cond.full_name, + true_value=self.prop1, + false_value=self.prop2, + is_prop=True, + ) diff --git a/pynecone/propcond.py b/pynecone/propcond.py deleted file mode 100644 index 1352dcae4..000000000 --- a/pynecone/propcond.py +++ /dev/null @@ -1,51 +0,0 @@ -"""Create a Prop Condition.""" -from typing import Any - -from pynecone import utils -from pynecone.base import Base -from pynecone.var import Var - - -class PropCond(Base): - """A conditional prop.""" - - # The condition to determine which prop to render. - cond: Var[Any] - - # The prop to render if the condition is true. - prop1: Any - - # The prop to render if the condition is false. - prop2: Any - - @classmethod - def create(cls, cond: Var, prop1: Any, prop2: Any = None): - """Create a conditional Prop. - - Args: - cond: The cond to determine which prop to render. - prop1: The prop value to render if the cond is true. - prop2: The prop value to render if the cond is false. - - Returns: - The conditional Prop. - """ - return cls( - cond=cond, - prop1=prop1, - prop2=prop2, - ) - - def __str__(self) -> str: - """Render the prop as a React string. - - Returns: - The React code to render the prop. - """ - assert self.cond is not None, "The condition must be set." - return utils.format_cond( - cond=self.cond.full_name, - true_value=self.prop1, - false_value=self.prop2, - is_prop=True, - ) diff --git a/tests/components/layout/test_cond.py b/tests/components/layout/test_cond.py index 8c956c94a..4a9e5b280 100644 --- a/tests/components/layout/test_cond.py +++ b/tests/components/layout/test_cond.py @@ -1,7 +1,12 @@ +from typing import Any + import pytest import pynecone as pc +from pynecone.components import cond from pynecone.components.layout.cond import Cond +from pynecone.components.layout.fragment import Fragment +from pynecone.components.tags.tag import PropCond from pynecone.components.typography.text import Text @@ -28,7 +33,7 @@ def test_validate_cond(cond_state: pc.Var): Args: cond_state: A fixture. """ - cond_component = Cond.create( + cond_component = cond( cond_state.value, Text.create("cond is True"), Text.create("cond is False"), @@ -39,3 +44,45 @@ def test_validate_cond(cond_state: pc.Var): "{`cond is True`} : " "{`cond is False`}}" ) + + +@pytest.mark.parametrize( + "c1, c2", + [ + (True, False), + (32, 0), + ("hello", ""), + (2.3, 0.0), + ], +) +def test_prop_cond(c1: Any, c2: Any): + """Test if cond can be a prop. + + Args: + c1: truth condition value + c2: false condition value + """ + prop_cond = cond( + True, + c1, + c2, + ) + + assert isinstance(prop_cond, PropCond) + assert prop_cond.prop1 == c1 + assert prop_cond.prop2 == c2 + assert prop_cond.cond == True + + +def test_cond_no_else(): + """Test if cond can be used without else""" + # Components should support the use of cond without else + comp = cond(True, Text.create("hello")) + assert isinstance(comp, Cond) + assert comp.cond == True + assert comp.comp1 == Text.create("hello") + assert comp.comp2 == Fragment.create() + + # Props do not support the use of cond without else + with pytest.raises(ValueError): + prop_cond = cond(True, "hello") diff --git a/tests/components/test_tag.py b/tests/components/test_tag.py index eb3229643..8593025ee 100644 --- a/tests/components/test_tag.py +++ b/tests/components/test_tag.py @@ -5,9 +5,9 @@ import pytest from pynecone.components import Box from pynecone.components.tags import CondTag, IterTag, Tag +from pynecone.components.tags.tag import PropCond from pynecone.event import EventChain, EventHandler, EventSpec from pynecone.var import BaseVar, Var -from pynecone.propcond import PropCond def mock_event(arg): diff --git a/tests/test_propcond.py b/tests/test_propcond.py index 3c90653a2..2019870bf 100644 --- a/tests/test_propcond.py +++ b/tests/test_propcond.py @@ -2,7 +2,7 @@ from typing import Any import pytest -from pynecone.propcond import PropCond +from pynecone.components.tags.tag import PropCond from pynecone.var import BaseVar, Var from pynecone.utils import wrap @@ -21,7 +21,6 @@ def test_validate_propcond(prop1: Any, prop2: Any): Args: prop1: truth condition value prop2: false condition value - """ prop_cond = PropCond.create( cond=BaseVar(name="cond_state.value", type_=str), prop1=prop1, prop2=prop2