소스 검색

Allow pc.cond without else case (#453)

Nikhil Rao 2 년 전
부모
커밋
8958f14778
6개의 변경된 파일126개의 추가작업 그리고 60개의 파일을 삭제
  1. 25 4
      pynecone/components/__init__.py
  2. 51 1
      pynecone/components/tags/tag.py
  3. 0 51
      pynecone/propcond.py
  4. 48 1
      tests/components/layout/test_cond.py
  5. 1 1
      tests/components/test_tag.py
  6. 1 2
      tests/test_propcond.py

+ 25 - 4
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)

+ 51 - 1
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,
+        )

+ 0 - 51
pynecone/propcond.py

@@ -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,
-        )

+ 48 - 1
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):
         "<Text>{`cond is True`}</Text> : "
         "<Text>{`cond is False`}</Text>}"
     )
+
+
+@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")

+ 1 - 1
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):

+ 1 - 2
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