Skip to content
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

Merged
merged 10 commits into from
Jan 28, 2023

Conversation

TommyDew42
Copy link
Contributor

To add and use pc.list/PcList to have list mutation detection.

It's not as straightforward as I thought. Or is my approach too complicated?

For #334. pc.dict would be in another PR.

pynecone/var.py Outdated
@@ -748,3 +748,54 @@ def type_(self):
if "return" in self.fget.__annotations__:
return self.fget.__annotations__["return"]
return Any


class PcList(list):
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PcList sounds more reasonable to me than just List cuz it's a list object specifically for pcnecone. IMO, List is appropriate when it is just a list but with more generalised capabilities.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup sounds good!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small nit, but PcList doesn't follow PEP-8 naming convention, which we should defer to when possible. Prefer PCList:

Note: When using acronyms in CapWords, capitalize all the letters of the acronym. Thus HTTPServerError is better than HttpServerError.

Source: PEP-8

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching that! fd7c722

pynecone/var.py Show resolved Hide resolved
So that mutation to them can be detected by the app:
* list
"""
for field in self.base_vars.values():
Copy link
Contributor

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 to PcList. Or state clearly that we only support this use case and the mutable notice still applies for the other case.

Copy link
Contributor Author

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 convert list to PcList. But now only list & dict would be iterated to recursively do the conversion.

* list
"""
for field in self.base_vars.values():
if field.type_ is list:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can use utils._issubclass(field.type_, List) here which will work for things like typing.List[int] also.

Also let's use List everywhere because we need to support Python 3.7+

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhhhh thanks for this suggestion. I also noticed the field.type_ in unit tests are all the types from typing instead of the native types.

pynecone/state.py Show resolved Hide resolved
pynecone/var.py Outdated
@@ -748,3 +748,54 @@ def type_(self):
if "return" in self.fget.__annotations__:
return self.fget.__annotations__["return"]
return Any


class PcList(list):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup sounds good!

"""
self._reassign_field = reassign_field

super().__init__(original_list)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put the super call first?

pynecone/var.py Show resolved Hide resolved
pynecone/var.py Outdated

def __init__(
self,
original_list: list,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use List[Any] here

* list
"""
for field in self.base_vars.values():
if field.type_ is list:
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhhhh thanks for this suggestion. I also noticed the field.type_ in unit tests are all the types from typing instead of the native types.

So that mutation to them can be detected by the app:
* list
"""
for field in self.base_vars.values():
Copy link
Contributor Author

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 convert list to PcList. But now only list & dict would be iterated to recursively do the conversion.

value = getattr(self, field.name)

value_in_pc_data = _convert_mutable_datatypes(
value, self._reassign_field, field.name
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found sth interesting. The previous way where we use lambda to bind the field.name to self._reassign_field wouldn't work. The field.name would be dynamically evaluated to be the last field.name instead of the field.name in the current iteration.

So let's say you have 3 fields in a state and the last state.name is friends_in_nested_list (i.e. the case in the unit test), the field_name would always be friends_in_nested_list. 😓 I have no choice but to pass both into the class, instead of use lambda to piece them together.

Any ideas for this problem?

Copy link
Contributor

Choose a reason for hiding this comment

The 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.

pynecone/var.py Show resolved Hide resolved
@picklelo
Copy link
Contributor

Sorry for the delay in reviewing this, will get to it soon!

Copy link
Contributor

@picklelo picklelo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome job! Tested this out on an app locally and it works nicely. May be some edge cases we need to fix in the future, but will go ahead and merge this in now :)

value = getattr(self, field.name)

value_in_pc_data = _convert_mutable_datatypes(
value, self._reassign_field, field.name
Copy link
Contributor

Choose a reason for hiding this comment

The 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.

value, self._reassign_field, field.name
)

if utils._issubclass(field.type_, List):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we create the value_in_pc_data inside this if statement? Since it's not used outside of it

pynecone/var.py Show resolved Hide resolved
@picklelo picklelo linked an issue Jan 28, 2023 that may be closed by this pull request
@picklelo picklelo merged commit d5a76f1 into reflex-dev:main Jan 28, 2023
@TommyDew42
Copy link
Contributor Author

@picklelo @kbrgl thanks for the reviews.

@picklelo What are the edge cases we haven't covered in this PR? Will have a follow up PR for them.

ACucos1 pushed a commit to ACucos1/rd-pynecone that referenced this pull request Feb 28, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Create pc.dict and pc.list
3 participants