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: #}
{# component: component dictionary #}
{% 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 %}
{{ render(child) }}
{% endfor %}

View File

@ -32,7 +32,7 @@ class Base(pydantic.BaseModel):
Returns:
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:
"""Set multiple fields and return the object.

View File

@ -1,7 +1,7 @@
"""Create a list of components from an iterable."""
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.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."""
# 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.
render_fn: Callable = Fragment.create
@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.
Args:
@ -34,7 +36,11 @@ class Foreach(Component):
TypeError: If the iterable is of type Any.
"""
try:
type_ = iterable.type_.__args__[0]
type_ = (
iterable.type_
if iterable.type_.mro()[0] == dict
else iterable.type_.__args__[0]
)
except Exception:
type_ = Any
iterable = Var.create(iterable) # type: ignore
@ -61,7 +67,11 @@ class Foreach(Component):
"""
tag = self._render()
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:
type_ = Any
arg = BaseVar(
@ -84,4 +94,5 @@ class Foreach(Component):
iterable_state=tag.iterable.full_name,
arg_name=arg.name,
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:
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]
# 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]

View File

@ -205,7 +205,7 @@ class Var(ABC):
):
if self.type_ == Any:
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(
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
num2: float = 3.14
key: str
map_key: str = "a"
array: List[float] = [1, 2, 3.14]
mapping: Dict[str, List[int]] = {"a": [1, 2, 3], "b": [4, 5, 6]}
obj: Object = Object()
@ -190,6 +191,7 @@ def test_class_vars(test_state):
"num1",
"num2",
"key",
"map_key",
"array",
"mapping",
"obj",
@ -281,6 +283,9 @@ def test_class_indexing_with_vars():
prop = TestState.mapping["a"][TestState.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():
"""Test that we can get class attributes."""