Foreach support for other data structures(dict, set, tuple) (#1029)

This commit is contained in:
Elijah Ahianyo 2023-05-17 23:11:41 +00:00 committed by GitHub
parent 0c33ad19f6
commit bb1cf4322e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 203 additions and 10 deletions

View File

@ -62,7 +62,7 @@
{# Args: #} {# Args: #}
{# component: component dictionary #} {# component: component dictionary #}
{% macro render_iterable_tag(component) %} {% macro render_iterable_tag(component) %}
{ {{- component.iterable_state }}.map(({{ component.arg_name }}, {{ component.arg_index }}) => ( { {%- if component.iterable_type == 'dict' -%}Object.entries({{- component.iterable_state }}){%- else -%}{{- component.iterable_state }}{%- endif -%}.map(({{ component.arg_name }}, {{ component.arg_index }}) => (
{% for child in component.children %} {% for child in component.children %}
{{ render(child) }} {{ render(child) }}
{% endfor %} {% endfor %}

View File

@ -32,7 +32,7 @@ class Base(pydantic.BaseModel):
Returns: Returns:
The object as a json string. The object as a json string.
""" """
return self.__config__.json_dumps(self.dict()) return self.__config__.json_dumps(self.dict(), default=list)
def set(self: PcType, **kwargs) -> PcType: def set(self: PcType, **kwargs) -> PcType:
"""Set multiple fields and return the object. """Set multiple fields and return the object.

View File

@ -1,7 +1,7 @@
"""Create a list of components from an iterable.""" """Create a list of components from an iterable."""
from __future__ import annotations from __future__ import annotations
from typing import Any, Callable, List from typing import Any, Callable, Dict, List, Set, Tuple, Union
from pynecone.components.component import Component from pynecone.components.component import Component
from pynecone.components.layout.fragment import Fragment from pynecone.components.layout.fragment import Fragment
@ -13,13 +13,15 @@ class Foreach(Component):
"""A component that takes in an iterable and a render function and renders a list of components.""" """A component that takes in an iterable and a render function and renders a list of components."""
# The iterable to create components from. # The iterable to create components from.
iterable: Var[List] iterable: Var[Union[List, Dict, Tuple, Set]]
# A function from the render args to the component. # A function from the render args to the component.
render_fn: Callable = Fragment.create render_fn: Callable = Fragment.create
@classmethod @classmethod
def create(cls, iterable: Var[List], render_fn: Callable, **props) -> Foreach: def create(
cls, iterable: Var[Union[List, Dict, Tuple, Set]], render_fn: Callable, **props
) -> Foreach:
"""Create a foreach component. """Create a foreach component.
Args: Args:
@ -34,7 +36,11 @@ class Foreach(Component):
TypeError: If the iterable is of type Any. TypeError: If the iterable is of type Any.
""" """
try: try:
type_ = iterable.type_.__args__[0] type_ = (
iterable.type_
if iterable.type_.mro()[0] == dict
else iterable.type_.__args__[0]
)
except Exception: except Exception:
type_ = Any type_ = Any
iterable = Var.create(iterable) # type: ignore iterable = Var.create(iterable) # type: ignore
@ -61,7 +67,11 @@ class Foreach(Component):
""" """
tag = self._render() tag = self._render()
try: try:
type_ = self.iterable.type_.__args__[0] type_ = (
self.iterable.type_
if self.iterable.type_.mro()[0] == dict
else self.iterable.type_.__args__[0]
)
except Exception: except Exception:
type_ = Any type_ = Any
arg = BaseVar( arg = BaseVar(
@ -84,4 +94,5 @@ class Foreach(Component):
iterable_state=tag.iterable.full_name, iterable_state=tag.iterable.full_name,
arg_name=arg.name, arg_name=arg.name,
arg_index=index_arg, arg_index=index_arg,
iterable_type=tag.iterable.type_.mro()[0].__name__,
) )

View File

@ -447,4 +447,4 @@ def json_dumps(obj: Any) -> str:
Returns: Returns:
A string A string
""" """
return json.dumps(obj, ensure_ascii=False) return json.dumps(obj, ensure_ascii=False, default=list)

View File

@ -12,7 +12,7 @@ from pynecone.base import Base
GenericType = Union[Type, _GenericAlias] GenericType = Union[Type, _GenericAlias]
# Valid state var types. # Valid state var types.
PrimitiveType = Union[int, float, bool, str, list, dict, tuple] PrimitiveType = Union[int, float, bool, str, list, dict, set, tuple]
StateVar = Union[PrimitiveType, Base, None] StateVar = Union[PrimitiveType, Base, None]

View File

@ -205,7 +205,7 @@ class Var(ABC):
): ):
if self.type_ == Any: if self.type_ == Any:
raise TypeError( raise TypeError(
f"Could not index into var of type Any. (If you are trying to index into a state var, add a type annotation to the var.)" f"Could not index into var of type Any. (If you are trying to index into a state var, add the correct type annotation to the var.)"
) )
raise TypeError( raise TypeError(
f"Var {self.name} of type {self.type_} does not support indexing." f"Var {self.name} of type {self.type_} does not support indexing."

View File

@ -0,0 +1,177 @@
from typing import Dict, List, Set, Tuple
import pytest
from pynecone.components import box, foreach, text
from pynecone.components.layout import Foreach
from pynecone.state import State
class ForEachState(State):
"""The test state."""
color_a: List[str] = ["red", "yellow"]
color_b: List[Dict[str, str]] = [
{
"name": "red",
},
{"name": "yellow"},
]
color_c: List[Dict[str, List[str]]] = [{"shades": ["light-red"]}]
color_d: Dict[str, str] = {"category": "primary", "name": "red"}
color_e: Dict[str, List[str]] = {
"red": ["orange", "yellow"],
"yellow": ["orange", "green"],
}
color_f: Dict[str, Dict[str, List[Dict[str, str]]]] = {
"primary": {"red": [{"shade": "dark"}]}
}
color_g: Tuple[str, str] = (
"red",
"yellow",
)
color_h: Set[str] = {"red", "green"}
def display_a(color):
return box(text(color))
def display_b(color):
return box(text(color["name"]))
def display_c(color):
return box(text(color["shades"][0]))
def display_d(color):
return box(text(color[0]), text(color[1]))
def display_e(color):
# color is a key-value pair list similar to `dict.items()`
return box(text(color[0]), text(color[1][0]))
def display_f(color):
return box(text(color[0]), text(color[1]["red"][0]["shade"]))
def show_item(item):
return text(item[1][0]["shade"])
def display_f1(color):
return box(text(foreach(color[1], show_item)))
def display_g(color):
return box(text(color))
def display_h(color):
return box(text(color))
@pytest.mark.parametrize(
"state_var, render_fn, render_dict",
[
(
ForEachState.color_a,
display_a,
{
"iterable_state": "for_each_state.color_a",
"arg_index": "i",
"iterable_type": "list",
},
),
(
ForEachState.color_b,
display_b,
{
"iterable_state": "for_each_state.color_b",
"arg_index": "i",
"iterable_type": "list",
},
),
(
ForEachState.color_c,
display_c,
{
"iterable_state": "for_each_state.color_c",
"arg_index": "i",
"iterable_type": "list",
},
),
(
ForEachState.color_d,
display_d,
{
"iterable_state": "for_each_state.color_d",
"arg_index": "i",
"iterable_type": "dict",
},
),
(
ForEachState.color_e,
display_e,
{
"iterable_state": "for_each_state.color_e",
"arg_index": "i",
"iterable_type": "dict",
},
),
(
ForEachState.color_f,
display_f,
{
"iterable_state": "for_each_state.color_f",
"arg_index": "i",
"iterable_type": "dict",
},
),
(
ForEachState.color_f,
display_f1,
{
"iterable_state": "for_each_state.color_f",
"arg_index": "i",
"iterable_type": "dict",
},
),
(
ForEachState.color_g,
display_g,
{
"iterable_state": "for_each_state.color_g",
"arg_index": "i",
"iterable_type": "tuple",
},
),
(
ForEachState.color_h,
display_h,
{
"iterable_state": "for_each_state.color_h",
"arg_index": "i",
"iterable_type": "set",
},
),
],
)
def test_foreach_render(state_var, render_fn, render_dict):
"""Test that the foreach component renders without error.
Args:
state_var: the state var.
render_fn: The render callable
render_dict: return dict on calling `component.render`
"""
component = Foreach.create(state_var, render_fn)
rend = component.render()
assert rend["iterable_state"] == render_dict["iterable_state"]
assert rend["arg_index"] == render_dict["arg_index"]
assert rend["iterable_type"] == render_dict["iterable_type"]

View File

@ -28,6 +28,7 @@ class TestState(State):
num1: int num1: int
num2: float = 3.14 num2: float = 3.14
key: str key: str
map_key: str = "a"
array: List[float] = [1, 2, 3.14] array: List[float] = [1, 2, 3.14]
mapping: Dict[str, List[int]] = {"a": [1, 2, 3], "b": [4, 5, 6]} mapping: Dict[str, List[int]] = {"a": [1, 2, 3], "b": [4, 5, 6]}
obj: Object = Object() obj: Object = Object()
@ -190,6 +191,7 @@ def test_class_vars(test_state):
"num1", "num1",
"num2", "num2",
"key", "key",
"map_key",
"array", "array",
"mapping", "mapping",
"obj", "obj",
@ -281,6 +283,9 @@ def test_class_indexing_with_vars():
prop = TestState.mapping["a"][TestState.num1] prop = TestState.mapping["a"][TestState.num1]
assert str(prop) == '{test_state.mapping["a"].at(test_state.num1)}' assert str(prop) == '{test_state.mapping["a"].at(test_state.num1)}'
prop = TestState.mapping[TestState.map_key]
assert str(prop) == "{test_state.mapping[test_state.map_key]}"
def test_class_attributes(): def test_class_attributes():
"""Test that we can get class attributes.""" """Test that we can get class attributes."""