Make PCList pickleable (#500)

This commit is contained in:
Elijah Ahianyo 2023-02-10 20:25:58 +00:00 committed by GitHub
parent 8d9c75824c
commit 69a9c95d73
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 155 additions and 132 deletions

14
poetry.lock generated
View File

@ -138,6 +138,18 @@ files = [
colorama = {version = "*", markers = "platform_system == \"Windows\""}
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
[[package]]
name = "cloudpickle"
version = "2.2.1"
description = "Extended pickling support for Python objects"
category = "main"
optional = false
python-versions = ">=3.6"
files = [
{file = "cloudpickle-2.2.1-py3-none-any.whl", hash = "sha256:61f594d1f4c295fa5cd9014ceb3a1fc4a70b0de1164b94fbc2d854ccba056f9f"},
{file = "cloudpickle-2.2.1.tar.gz", hash = "sha256:d89684b8de9e34a2a43b3460fbca07d09d6e25ce858df4d5a44240403b6178f5"},
]
[[package]]
name = "colorama"
version = "0.4.6"
@ -1198,4 +1210,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools"
[metadata]
lock-version = "2.0"
python-versions = "^3.7"
content-hash = "0a0cbe4cdf4f07a69b36c76988a3ae25f7d3856a6ab40ace5ee0682c357723d8"
content-hash = "5be82a91acbdb6df10d3c020b569c583a319905362e94a09c57a55892e1e98dd"

View File

@ -3,17 +3,17 @@ from __future__ import annotations
import asyncio
import functools
import pickle
import traceback
from abc import ABC
from typing import Any, Callable, ClassVar, Dict, List, Optional, Sequence, Set, Type
import cloudpickle
from redis import Redis
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]
@ -678,7 +678,7 @@ class StateManager(Base):
if redis_state is None:
self.set_state(token, self.state())
return self.get_state(token)
return pickle.loads(redis_state)
return cloudpickle.loads(redis_state)
if token not in self.states:
self.states[token] = self.state()
@ -693,7 +693,7 @@ class StateManager(Base):
"""
if self.redis is None:
return
self.redis.set(token, pickle.dumps(state), ex=self.token_expiration)
self.redis.set(token, cloudpickle.dumps(state), ex=self.token_expiration)
def _convert_mutable_datatypes(
@ -712,17 +712,15 @@ def _convert_mutable_datatypes(
Returns:
The converted field_value
"""
# TODO: The PCList class needs to be pickleable to work with Redis.
# We will uncomment this code once this is fixed.
# 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
# )
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
# )
field_value = PCList(
field_value, reassign_field=reassign_field, field_name=field_name
)
if isinstance(field_value, dict):
for key, value in field_value.items():

View File

@ -821,7 +821,7 @@ class PCList(list):
kargs: The kwargs passed.
"""
super().extend(*args, **kargs)
self._reassign_field()
self._reassign_field() if hasattr(self, "_reassign_field") else None
def pop(self, *args, **kargs):
"""Remove an element.

View File

@ -37,6 +37,7 @@ httpx = "^0.23.1"
python-socketio = "^5.7.2"
psutil = "^5.9.4"
websockets = "^10.4"
cloudpickle = "^2.2.1"
[tool.poetry.dev-dependencies]
pytest = "^7.1.2"

View File

@ -1,10 +1,11 @@
import os.path
from typing import Type
from typing import List, Tuple, Type
import pytest
from pynecone.app import App, DefaultState
from pynecone.components import Box
from pynecone.event import Event
from pynecone.middleware import HydrateMiddleware
from pynecone.state import State
from pynecone.style import Style
@ -226,119 +227,119 @@ def list_mutation_state():
return TestState()
# @pytest.mark.asyncio
# @pytest.mark.parametrize(
# "event_tuples",
# [
# pytest.param(
# [
# (
# "test_state.make_friend",
# {"test_state": {"plain_friends": ["Tommy", "another-fd"]}},
# ),
# (
# "test_state.change_first_friend",
# {"test_state": {"plain_friends": ["Jenny", "another-fd"]}},
# ),
# ],
# id="append then __setitem__",
# ),
# pytest.param(
# [
# (
# "test_state.unfriend_first_friend",
# {"test_state": {"plain_friends": []}},
# ),
# (
# "test_state.make_friend",
# {"test_state": {"plain_friends": ["another-fd"]}},
# ),
# ],
# id="delitem then append",
# ),
# pytest.param(
# [
# (
# "test_state.make_friends_with_colleagues",
# {"test_state": {"plain_friends": ["Tommy", "Peter", "Jimmy"]}},
# ),
# (
# "test_state.remove_tommy",
# {"test_state": {"plain_friends": ["Peter", "Jimmy"]}},
# ),
# (
# "test_state.remove_last_friend",
# {"test_state": {"plain_friends": ["Peter"]}},
# ),
# (
# "test_state.unfriend_all_friends",
# {"test_state": {"plain_friends": []}},
# ),
# ],
# id="extend, remove, pop, clear",
# ),
# pytest.param(
# [
# (
# "test_state.add_jimmy_to_second_group",
# {
# "test_state": {
# "friends_in_nested_list": [["Tommy"], ["Jenny", "Jimmy"]]
# }
# },
# ),
# (
# "test_state.remove_first_person_from_first_group",
# {
# "test_state": {
# "friends_in_nested_list": [[], ["Jenny", "Jimmy"]]
# }
# },
# ),
# (
# "test_state.remove_first_group",
# {"test_state": {"friends_in_nested_list": [["Jenny", "Jimmy"]]}},
# ),
# ],
# id="nested list",
# ),
# pytest.param(
# [
# (
# "test_state.add_jimmy_to_tommy_friends",
# {"test_state": {"friends_in_dict": {"Tommy": ["Jenny", "Jimmy"]}}},
# ),
# (
# "test_state.remove_jenny_from_tommy",
# {"test_state": {"friends_in_dict": {"Tommy": ["Jimmy"]}}},
# ),
# (
# "test_state.tommy_has_no_fds",
# {"test_state": {"friends_in_dict": {"Tommy": []}}},
# ),
# ],
# id="list in dict",
# ),
# ],
# )
# async def test_list_mutation_detection__plain_list(
# event_tuples: List[Tuple[str, List[str]]], list_mutation_state: State
# ):
# """Test list mutation detection
# when reassignment is not explicitly included in the logic.
@pytest.mark.asyncio
@pytest.mark.parametrize(
"event_tuples",
[
pytest.param(
[
(
"test_state.make_friend",
{"test_state": {"plain_friends": ["Tommy", "another-fd"]}},
),
(
"test_state.change_first_friend",
{"test_state": {"plain_friends": ["Jenny", "another-fd"]}},
),
],
id="append then __setitem__",
),
pytest.param(
[
(
"test_state.unfriend_first_friend",
{"test_state": {"plain_friends": []}},
),
(
"test_state.make_friend",
{"test_state": {"plain_friends": ["another-fd"]}},
),
],
id="delitem then append",
),
pytest.param(
[
(
"test_state.make_friends_with_colleagues",
{"test_state": {"plain_friends": ["Tommy", "Peter", "Jimmy"]}},
),
(
"test_state.remove_tommy",
{"test_state": {"plain_friends": ["Peter", "Jimmy"]}},
),
(
"test_state.remove_last_friend",
{"test_state": {"plain_friends": ["Peter"]}},
),
(
"test_state.unfriend_all_friends",
{"test_state": {"plain_friends": []}},
),
],
id="extend, remove, pop, clear",
),
pytest.param(
[
(
"test_state.add_jimmy_to_second_group",
{
"test_state": {
"friends_in_nested_list": [["Tommy"], ["Jenny", "Jimmy"]]
}
},
),
(
"test_state.remove_first_person_from_first_group",
{
"test_state": {
"friends_in_nested_list": [[], ["Jenny", "Jimmy"]]
}
},
),
(
"test_state.remove_first_group",
{"test_state": {"friends_in_nested_list": [["Jenny", "Jimmy"]]}},
),
],
id="nested list",
),
pytest.param(
[
(
"test_state.add_jimmy_to_tommy_friends",
{"test_state": {"friends_in_dict": {"Tommy": ["Jenny", "Jimmy"]}}},
),
(
"test_state.remove_jenny_from_tommy",
{"test_state": {"friends_in_dict": {"Tommy": ["Jimmy"]}}},
),
(
"test_state.tommy_has_no_fds",
{"test_state": {"friends_in_dict": {"Tommy": []}}},
),
],
id="list in dict",
),
],
)
async def test_list_mutation_detection__plain_list(
event_tuples: List[Tuple[str, List[str]]], list_mutation_state: State
):
"""Test list mutation detection
when reassignment is not explicitly included in the logic.
# Args:
# event_tuples: From parametrization.
# list_mutation_state: A state with list mutation features.
# """
# for event_name, expected_delta in event_tuples:
# result = await list_mutation_state.process(
# Event(
# token="fake-token",
# name=event_name,
# router_data={"pathname": "/", "query": {}},
# payload={},
# )
# )
Args:
event_tuples: From parametrization.
list_mutation_state: A state with list mutation features.
"""
for event_name, expected_delta in event_tuples:
result = await list_mutation_state.process(
Event(
token="fake-token",
name=event_name,
router_data={"pathname": "/", "query": {}},
payload={},
)
)
# assert result.delta == expected_delta
assert result.delta == expected_delta

View File

@ -1,9 +1,10 @@
from typing import Dict, List
import cloudpickle
import pytest
from pynecone.base import Base
from pynecone.var import BaseVar, Var
from pynecone.var import BaseVar, PCList, Var
test_vars = [
BaseVar(name="prop1", type_=int),
@ -207,3 +208,13 @@ def test_dict_indexing():
# Check correct indexing.
assert str(dct["a"]) == '{dct["a"]}'
assert str(dct["asdf"]) == '{dct["asdf"]}'
def test_pickleable_pc_list():
"""Test that PCList is pickleable."""
pc_list = PCList(
original_list=[1, 2, 3], reassign_field=lambda x: x, field_name="random"
)
pickled_list = cloudpickle.dumps(pc_list)
assert cloudpickle.loads(pickled_list) == pc_list