Make PCList pickleable (#500)
This commit is contained in:
parent
8d9c75824c
commit
69a9c95d73
14
poetry.lock
generated
14
poetry.lock
generated
@ -138,6 +138,18 @@ files = [
|
|||||||
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
colorama = {version = "*", markers = "platform_system == \"Windows\""}
|
||||||
importlib-metadata = {version = "*", markers = "python_version < \"3.8\""}
|
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]]
|
[[package]]
|
||||||
name = "colorama"
|
name = "colorama"
|
||||||
version = "0.4.6"
|
version = "0.4.6"
|
||||||
@ -1198,4 +1210,4 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools"
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.7"
|
python-versions = "^3.7"
|
||||||
content-hash = "0a0cbe4cdf4f07a69b36c76988a3ae25f7d3856a6ab40ace5ee0682c357723d8"
|
content-hash = "5be82a91acbdb6df10d3c020b569c583a319905362e94a09c57a55892e1e98dd"
|
||||||
|
@ -3,17 +3,17 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import functools
|
import functools
|
||||||
import pickle
|
|
||||||
import traceback
|
import traceback
|
||||||
from abc import ABC
|
from abc import ABC
|
||||||
from typing import Any, Callable, ClassVar, Dict, List, Optional, Sequence, Set, Type
|
from typing import Any, Callable, ClassVar, Dict, List, Optional, Sequence, Set, Type
|
||||||
|
|
||||||
|
import cloudpickle
|
||||||
from redis import Redis
|
from redis import Redis
|
||||||
|
|
||||||
from pynecone import constants, utils
|
from pynecone import constants, utils
|
||||||
from pynecone.base import Base
|
from pynecone.base import Base
|
||||||
from pynecone.event import Event, EventHandler, window_alert
|
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]
|
Delta = Dict[str, Any]
|
||||||
|
|
||||||
@ -678,7 +678,7 @@ class StateManager(Base):
|
|||||||
if redis_state is None:
|
if redis_state is None:
|
||||||
self.set_state(token, self.state())
|
self.set_state(token, self.state())
|
||||||
return self.get_state(token)
|
return self.get_state(token)
|
||||||
return pickle.loads(redis_state)
|
return cloudpickle.loads(redis_state)
|
||||||
|
|
||||||
if token not in self.states:
|
if token not in self.states:
|
||||||
self.states[token] = self.state()
|
self.states[token] = self.state()
|
||||||
@ -693,7 +693,7 @@ class StateManager(Base):
|
|||||||
"""
|
"""
|
||||||
if self.redis is None:
|
if self.redis is None:
|
||||||
return
|
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(
|
def _convert_mutable_datatypes(
|
||||||
@ -712,17 +712,15 @@ def _convert_mutable_datatypes(
|
|||||||
Returns:
|
Returns:
|
||||||
The converted field_value
|
The converted field_value
|
||||||
"""
|
"""
|
||||||
# TODO: The PCList class needs to be pickleable to work with Redis.
|
if isinstance(field_value, list):
|
||||||
# We will uncomment this code once this is fixed.
|
for index in range(len(field_value)):
|
||||||
# if isinstance(field_value, list):
|
field_value[index] = _convert_mutable_datatypes(
|
||||||
# for index in range(len(field_value)):
|
field_value[index], reassign_field, field_name
|
||||||
# field_value[index] = _convert_mutable_datatypes(
|
)
|
||||||
# field_value[index], reassign_field, field_name
|
|
||||||
# )
|
|
||||||
|
|
||||||
# field_value = PCList(
|
field_value = PCList(
|
||||||
# field_value, reassign_field=reassign_field, field_name=field_name
|
field_value, reassign_field=reassign_field, field_name=field_name
|
||||||
# )
|
)
|
||||||
|
|
||||||
if isinstance(field_value, dict):
|
if isinstance(field_value, dict):
|
||||||
for key, value in field_value.items():
|
for key, value in field_value.items():
|
||||||
|
@ -821,7 +821,7 @@ class PCList(list):
|
|||||||
kargs: The kwargs passed.
|
kargs: The kwargs passed.
|
||||||
"""
|
"""
|
||||||
super().extend(*args, **kargs)
|
super().extend(*args, **kargs)
|
||||||
self._reassign_field()
|
self._reassign_field() if hasattr(self, "_reassign_field") else None
|
||||||
|
|
||||||
def pop(self, *args, **kargs):
|
def pop(self, *args, **kargs):
|
||||||
"""Remove an element.
|
"""Remove an element.
|
||||||
|
@ -37,6 +37,7 @@ httpx = "^0.23.1"
|
|||||||
python-socketio = "^5.7.2"
|
python-socketio = "^5.7.2"
|
||||||
psutil = "^5.9.4"
|
psutil = "^5.9.4"
|
||||||
websockets = "^10.4"
|
websockets = "^10.4"
|
||||||
|
cloudpickle = "^2.2.1"
|
||||||
|
|
||||||
[tool.poetry.dev-dependencies]
|
[tool.poetry.dev-dependencies]
|
||||||
pytest = "^7.1.2"
|
pytest = "^7.1.2"
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import os.path
|
import os.path
|
||||||
from typing import Type
|
from typing import List, Tuple, Type
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from pynecone.app import App, DefaultState
|
from pynecone.app import App, DefaultState
|
||||||
from pynecone.components import Box
|
from pynecone.components import Box
|
||||||
|
from pynecone.event import Event
|
||||||
from pynecone.middleware import HydrateMiddleware
|
from pynecone.middleware import HydrateMiddleware
|
||||||
from pynecone.state import State
|
from pynecone.state import State
|
||||||
from pynecone.style import Style
|
from pynecone.style import Style
|
||||||
@ -226,119 +227,119 @@ def list_mutation_state():
|
|||||||
return TestState()
|
return TestState()
|
||||||
|
|
||||||
|
|
||||||
# @pytest.mark.asyncio
|
@pytest.mark.asyncio
|
||||||
# @pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
# "event_tuples",
|
"event_tuples",
|
||||||
# [
|
[
|
||||||
# pytest.param(
|
pytest.param(
|
||||||
# [
|
[
|
||||||
# (
|
(
|
||||||
# "test_state.make_friend",
|
"test_state.make_friend",
|
||||||
# {"test_state": {"plain_friends": ["Tommy", "another-fd"]}},
|
{"test_state": {"plain_friends": ["Tommy", "another-fd"]}},
|
||||||
# ),
|
),
|
||||||
# (
|
(
|
||||||
# "test_state.change_first_friend",
|
"test_state.change_first_friend",
|
||||||
# {"test_state": {"plain_friends": ["Jenny", "another-fd"]}},
|
{"test_state": {"plain_friends": ["Jenny", "another-fd"]}},
|
||||||
# ),
|
),
|
||||||
# ],
|
],
|
||||||
# id="append then __setitem__",
|
id="append then __setitem__",
|
||||||
# ),
|
),
|
||||||
# pytest.param(
|
pytest.param(
|
||||||
# [
|
[
|
||||||
# (
|
(
|
||||||
# "test_state.unfriend_first_friend",
|
"test_state.unfriend_first_friend",
|
||||||
# {"test_state": {"plain_friends": []}},
|
{"test_state": {"plain_friends": []}},
|
||||||
# ),
|
),
|
||||||
# (
|
(
|
||||||
# "test_state.make_friend",
|
"test_state.make_friend",
|
||||||
# {"test_state": {"plain_friends": ["another-fd"]}},
|
{"test_state": {"plain_friends": ["another-fd"]}},
|
||||||
# ),
|
),
|
||||||
# ],
|
],
|
||||||
# id="delitem then append",
|
id="delitem then append",
|
||||||
# ),
|
),
|
||||||
# pytest.param(
|
pytest.param(
|
||||||
# [
|
[
|
||||||
# (
|
(
|
||||||
# "test_state.make_friends_with_colleagues",
|
"test_state.make_friends_with_colleagues",
|
||||||
# {"test_state": {"plain_friends": ["Tommy", "Peter", "Jimmy"]}},
|
{"test_state": {"plain_friends": ["Tommy", "Peter", "Jimmy"]}},
|
||||||
# ),
|
),
|
||||||
# (
|
(
|
||||||
# "test_state.remove_tommy",
|
"test_state.remove_tommy",
|
||||||
# {"test_state": {"plain_friends": ["Peter", "Jimmy"]}},
|
{"test_state": {"plain_friends": ["Peter", "Jimmy"]}},
|
||||||
# ),
|
),
|
||||||
# (
|
(
|
||||||
# "test_state.remove_last_friend",
|
"test_state.remove_last_friend",
|
||||||
# {"test_state": {"plain_friends": ["Peter"]}},
|
{"test_state": {"plain_friends": ["Peter"]}},
|
||||||
# ),
|
),
|
||||||
# (
|
(
|
||||||
# "test_state.unfriend_all_friends",
|
"test_state.unfriend_all_friends",
|
||||||
# {"test_state": {"plain_friends": []}},
|
{"test_state": {"plain_friends": []}},
|
||||||
# ),
|
),
|
||||||
# ],
|
],
|
||||||
# id="extend, remove, pop, clear",
|
id="extend, remove, pop, clear",
|
||||||
# ),
|
),
|
||||||
# pytest.param(
|
pytest.param(
|
||||||
# [
|
[
|
||||||
# (
|
(
|
||||||
# "test_state.add_jimmy_to_second_group",
|
"test_state.add_jimmy_to_second_group",
|
||||||
# {
|
{
|
||||||
# "test_state": {
|
"test_state": {
|
||||||
# "friends_in_nested_list": [["Tommy"], ["Jenny", "Jimmy"]]
|
"friends_in_nested_list": [["Tommy"], ["Jenny", "Jimmy"]]
|
||||||
# }
|
}
|
||||||
# },
|
},
|
||||||
# ),
|
),
|
||||||
# (
|
(
|
||||||
# "test_state.remove_first_person_from_first_group",
|
"test_state.remove_first_person_from_first_group",
|
||||||
# {
|
{
|
||||||
# "test_state": {
|
"test_state": {
|
||||||
# "friends_in_nested_list": [[], ["Jenny", "Jimmy"]]
|
"friends_in_nested_list": [[], ["Jenny", "Jimmy"]]
|
||||||
# }
|
}
|
||||||
# },
|
},
|
||||||
# ),
|
),
|
||||||
# (
|
(
|
||||||
# "test_state.remove_first_group",
|
"test_state.remove_first_group",
|
||||||
# {"test_state": {"friends_in_nested_list": [["Jenny", "Jimmy"]]}},
|
{"test_state": {"friends_in_nested_list": [["Jenny", "Jimmy"]]}},
|
||||||
# ),
|
),
|
||||||
# ],
|
],
|
||||||
# id="nested list",
|
id="nested list",
|
||||||
# ),
|
),
|
||||||
# pytest.param(
|
pytest.param(
|
||||||
# [
|
[
|
||||||
# (
|
(
|
||||||
# "test_state.add_jimmy_to_tommy_friends",
|
"test_state.add_jimmy_to_tommy_friends",
|
||||||
# {"test_state": {"friends_in_dict": {"Tommy": ["Jenny", "Jimmy"]}}},
|
{"test_state": {"friends_in_dict": {"Tommy": ["Jenny", "Jimmy"]}}},
|
||||||
# ),
|
),
|
||||||
# (
|
(
|
||||||
# "test_state.remove_jenny_from_tommy",
|
"test_state.remove_jenny_from_tommy",
|
||||||
# {"test_state": {"friends_in_dict": {"Tommy": ["Jimmy"]}}},
|
{"test_state": {"friends_in_dict": {"Tommy": ["Jimmy"]}}},
|
||||||
# ),
|
),
|
||||||
# (
|
(
|
||||||
# "test_state.tommy_has_no_fds",
|
"test_state.tommy_has_no_fds",
|
||||||
# {"test_state": {"friends_in_dict": {"Tommy": []}}},
|
{"test_state": {"friends_in_dict": {"Tommy": []}}},
|
||||||
# ),
|
),
|
||||||
# ],
|
],
|
||||||
# id="list in dict",
|
id="list in dict",
|
||||||
# ),
|
),
|
||||||
# ],
|
],
|
||||||
# )
|
)
|
||||||
# async def test_list_mutation_detection__plain_list(
|
async def test_list_mutation_detection__plain_list(
|
||||||
# event_tuples: List[Tuple[str, List[str]]], list_mutation_state: State
|
event_tuples: List[Tuple[str, List[str]]], list_mutation_state: State
|
||||||
# ):
|
):
|
||||||
# """Test list mutation detection
|
"""Test list mutation detection
|
||||||
# when reassignment is not explicitly included in the logic.
|
when reassignment is not explicitly included in the logic.
|
||||||
|
|
||||||
# Args:
|
Args:
|
||||||
# event_tuples: From parametrization.
|
event_tuples: From parametrization.
|
||||||
# list_mutation_state: A state with list mutation features.
|
list_mutation_state: A state with list mutation features.
|
||||||
# """
|
"""
|
||||||
# for event_name, expected_delta in event_tuples:
|
for event_name, expected_delta in event_tuples:
|
||||||
# result = await list_mutation_state.process(
|
result = await list_mutation_state.process(
|
||||||
# Event(
|
Event(
|
||||||
# token="fake-token",
|
token="fake-token",
|
||||||
# name=event_name,
|
name=event_name,
|
||||||
# router_data={"pathname": "/", "query": {}},
|
router_data={"pathname": "/", "query": {}},
|
||||||
# payload={},
|
payload={},
|
||||||
# )
|
)
|
||||||
# )
|
)
|
||||||
|
|
||||||
# assert result.delta == expected_delta
|
assert result.delta == expected_delta
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
|
||||||
|
import cloudpickle
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from pynecone.base import Base
|
from pynecone.base import Base
|
||||||
from pynecone.var import BaseVar, Var
|
from pynecone.var import BaseVar, PCList, Var
|
||||||
|
|
||||||
test_vars = [
|
test_vars = [
|
||||||
BaseVar(name="prop1", type_=int),
|
BaseVar(name="prop1", type_=int),
|
||||||
@ -207,3 +208,13 @@ def test_dict_indexing():
|
|||||||
# Check correct indexing.
|
# Check correct indexing.
|
||||||
assert str(dct["a"]) == '{dct["a"]}'
|
assert str(dct["a"]) == '{dct["a"]}'
|
||||||
assert str(dct["asdf"]) == '{dct["asdf"]}'
|
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
|
||||||
|
Loading…
Reference in New Issue
Block a user