-
Notifications
You must be signed in to change notification settings - Fork 994
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add and use pc.list
to have list mutation detection
#339
Changes from 9 commits
c964e49
579833c
f95de4c
7ed7ca5
b1bc892
f58db52
e2dbc36
71848e2
0b68e4a
fd7c722
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,7 +13,7 @@ | |
from pynecone import constants, utils | ||
from pynecone.base import Base | ||
from pynecone.event import Event, EventHandler, window_alert | ||
from pynecone.var import BaseVar, ComputedVar, Var | ||
from pynecone.var import BaseVar, ComputedVar, PcList, Var | ||
|
||
Delta = Dict[str, Any] | ||
|
||
|
@@ -61,6 +61,40 @@ def __init__(self, *args, **kwargs): | |
for substate in self.get_substates(): | ||
self.substates[substate.get_name()] = substate().set(parent_state=self) | ||
|
||
self._init_mutable_fields() | ||
|
||
def _init_mutable_fields(self): | ||
"""Initialize mutable fields. | ||
|
||
So that mutation to them can be detected by the app: | ||
* list | ||
""" | ||
for field in self.base_vars.values(): | ||
value = getattr(self, field.name) | ||
|
||
value_in_pc_data = _convert_mutable_datatypes( | ||
value, self._reassign_field, field.name | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I found sth interesting. The previous way where we use So let's say you have 3 fields in a state and the last Any ideas for this problem? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ahh I think @Lendemor was seeing a similar issue in handling the dynamic routing - I think this code may be relevant where he used a factory? https://github.com/pynecone-io/pynecone/blob/5aae6a122d8935aea599b5a9f8bf84622fda9cdb/pynecone/state.py#L291 I'm not entirely sure though may have to give this a deeper look. |
||
) | ||
|
||
if utils._issubclass(field.type_, List): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we create the |
||
setattr(self, field.name, value_in_pc_data) | ||
|
||
self.clean() | ||
|
||
def _reassign_field(self, field_name: str): | ||
picklelo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""Reassign the given field. | ||
|
||
Primarily for mutation in fields of mutable data types. | ||
|
||
Args: | ||
field_name (str): The name of the field we want to reassign | ||
""" | ||
setattr( | ||
self, | ||
field_name, | ||
getattr(self, field_name), | ||
) | ||
|
||
def __repr__(self) -> str: | ||
"""Get the string representation of the state. | ||
|
||
|
@@ -578,3 +612,38 @@ def set_state(self, token: str, state: State): | |
if self.redis is None: | ||
return | ||
self.redis.set(token, pickle.dumps(state), ex=self.token_expiration) | ||
|
||
|
||
def _convert_mutable_datatypes( | ||
field_value: Any, reassign_field: Callable, field_name: str | ||
) -> Any: | ||
"""Recursively convert mutable data to the Pc data types. | ||
|
||
Note: right now only list & dict would be handled recursively. | ||
|
||
Args: | ||
field_value: The target field_value. | ||
reassign_field: | ||
The function to reassign the field in the parent state. | ||
field_name: the name of the field in the parent state | ||
|
||
Returns: | ||
The converted field_value | ||
""" | ||
if isinstance(field_value, list): | ||
for index in range(len(field_value)): | ||
field_value[index] = _convert_mutable_datatypes( | ||
field_value[index], reassign_field, field_name | ||
) | ||
|
||
field_value = PcList( | ||
field_value, reassign_field=reassign_field, field_name=field_name | ||
) | ||
|
||
elif isinstance(field_value, dict): | ||
for key, value in field_value.items(): | ||
field_value[key] = _convert_mutable_datatypes( | ||
value, reassign_field, field_name | ||
) | ||
|
||
return field_value |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -748,3 +748,96 @@ def type_(self): | |
if "return" in self.fget.__annotations__: | ||
return self.fget.__annotations__["return"] | ||
return Any | ||
|
||
|
||
class PcList(list): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yup sounds good! There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Small nit, but
Source: PEP-8 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for catching that! fd7c722 |
||
"""A custom list that pynecone can detect its mutation.""" | ||
|
||
def __init__( | ||
self, | ||
original_list: List, | ||
reassign_field: Callable = lambda _field_name: None, | ||
field_name: str = "", | ||
): | ||
"""Initialize PcList. | ||
|
||
Args: | ||
original_list (List): The original list | ||
reassign_field (Callable): | ||
The method in the parent state to reassign the field. | ||
Default to be a no-op function | ||
field_name (str): the name of field in the parent state | ||
""" | ||
self._reassign_field = lambda: reassign_field(field_name) | ||
|
||
super().__init__(original_list) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Put the super call first? |
||
|
||
def append(self, *args, **kargs): | ||
"""Append. | ||
|
||
Args: | ||
args: The args passed. | ||
kargs: The kwargs passed. | ||
""" | ||
super().append(*args, **kargs) | ||
self._reassign_field() | ||
|
||
def __setitem__(self, *args, **kargs): | ||
"""Set item. | ||
|
||
Args: | ||
args: The args passed. | ||
kargs: The kwargs passed. | ||
""" | ||
super().__setitem__(*args, **kargs) | ||
self._reassign_field() | ||
|
||
def __delitem__(self, *args, **kargs): | ||
"""Delete item. | ||
|
||
Args: | ||
args: The args passed. | ||
kargs: The kwargs passed. | ||
""" | ||
super().__delitem__(*args, **kargs) | ||
self._reassign_field() | ||
picklelo marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
def clear(self, *args, **kargs): | ||
"""Remove all item from the list. | ||
|
||
Args: | ||
args: The args passed. | ||
kargs: The kwargs passed. | ||
""" | ||
super().clear(*args, **kargs) | ||
self._reassign_field() | ||
|
||
def extend(self, *args, **kargs): | ||
"""Add all item of a list to the end of the list. | ||
|
||
Args: | ||
args: The args passed. | ||
kargs: The kwargs passed. | ||
""" | ||
super().extend(*args, **kargs) | ||
self._reassign_field() | ||
|
||
def pop(self, *args, **kargs): | ||
"""Remove an element. | ||
|
||
Args: | ||
args: The args passed. | ||
kargs: The kwargs passed. | ||
""" | ||
super().pop(*args, **kargs) | ||
self._reassign_field() | ||
|
||
def remove(self, *args, **kargs): | ||
"""Remove an element. | ||
|
||
Args: | ||
args: The args passed. | ||
kargs: The kwargs passed. | ||
""" | ||
super().remove(*args, **kargs) | ||
self._reassign_field() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will work for the common use case - one thing we need to think deeper about is how to treat nested types.
For example, a dict with a list value, a subclass of
pc.Base
with a list field, etc. We may later need to add some sort of recursive inspection and convert everything toPcList
. Or state clearly that we only support this use case and the mutable notice still applies for the other case.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Created
_convert_mutable_datatypes
in e2dbc36 to recursively convertlist
toPcList
. But now onlylist
&dict
would be iterated to recursively do the conversion.