Repository navigation
Two way serialization - #1784
workaholicAB wants to merge 48 commits into
Conversation
akksi
left a comment
There was a problem hiding this comment.
It looks and works well overall as far as I can tell, but seems like it doesn't convert callback inputs back to the original datatype, e.g. callbacks receive data as JSON in {"__type": "DataFrame", "__value": "xyz"} format instead of actual Pandas DataFrame objects.
|
@workaholicpanda @akksi @chriddyp Sorry for bumping into this PR, but this looks interesting! 🙂 If I understand this PR correctly, you want to try to support serialization/deserialization of custom Python objects before/after data is sent/received on the server. If so this is great, and something we have seen the need of as well. 🎉 Looking at the draft PR, it looks like the current sketch is to centralize all serialization in a |
|
@anders-kiaer - Thanks for talking a look! Yes the idea here is to support custom python objects but also to deserialize these objects in callbacks. So if you pass a dataframe into You are correct that this is "global". The components end up receiving data in the same format as before (JSON), we're just handling more object to JSON and JSON back to object. It would be great to have this on a per-component level. Also for it to be pluggable. We might be able to build some of that in to this PR while we're here. Could you describe some use cases of both component and data types that you'd like to have custom serializers/deserializers for? |
The overall pattern would be components that require a complex data set, which would be easier to modify/adjust/interact with/give as input on the Python side through an appropriate Python object compared with a JSON structure which typically could be structured to make most sense/be most performant in the frontend (React) component. Here is an attempt on finding a wide distribution of concrete examples:
These are the ones I can think of at the moment, but I'm quite sure the community will discover more of them 🙂
Multiple valid input formats with serialization/deserialization on component level could perhaps take inspiration from this PR with the from dash.development.base_component import Component
class Store(Component):
@classmethod
def serialize(prop_name, value: typing.Union[pd.DataFrame, JSONStructure]):
if isinstance(pd.DataFrame):
return {"__type": "DataFrame", "data": df.to_json()} # The frontend component itself now needs to handle the extra layer/key `__type`, but since this function is part of the component itself it should be easy to keep in sync
return {"__type": "JSONStructure, "data": to_json(value} # Not a df - serialize as today
@classmethod
def deserialize(prop_name, value) -> typing.Union[pd.DataFrame, JSONStructure]:
if value["__type"] == "DataFrame":
return pd.read_json(value["data"])
return from_json(value["data"])Besides the flexibility component level serialization/deserialization gives (together with easier to understand error messages wrt. invalid input data since it could be given from Python when the data is closer to the user input format), I think one of the biggest benefit long term having it on component level would probably be the type annotations (which could give the Dash framework automatic documentation building of props on components and their valid Python input formats not relying entirely on docstrings + live editor feedback to the dash app developer through standard Python static type check tools). |
|
^^ Similar to what Anders says, component level serialization would simplify and make a lot of components in dash-bio faster. It would also reduce user error/frustration as files from different bio-software don't always come in the same format. Just under half of the dash-bio apps rely on JSON formats (Molecule3D, Needleplot, OncoPrint, Circos, Ideogram, Pileup), with the rest being csvs, Fasta, or PDB. Fasta and PDB files are the only files with a set structure, but they are large files, and serialization/deserialization to cache relevant parts would make the interactivity faster. All other files need to be structured perfectly to return the desired visualization, so component-level serialization/deserialization would help a lot. |
|
@anders-kiaer @emmeb Thanks for sharing your thoughts on the PR. I like the idea that throw the serialization job out to the Dash user because they should know much better what they're dealing with, what they want to send to client/server and vice versa. The main motive of this PR, of course requested by @chriddyp, probably came because of potential data-loss & type-loss. Overall, I think we can merge two ideas like below.
This is solely my thoughts that needs to be validated and confirmed by @chriddyp for sure. Thanks for reading! |
…sh; branch 'dev' of github.com:plotly/dash into two-way-serialization
|
Thanks @workaholicpanda for expanding the serialization possibilities in Dash 🙂👏
If I understand those two commits correctly, the dash app developer would then do something like: import pandas as pd
import dash_bio
df = pd.read_csv("some.csv")
data = # use df input above, but in some way add new bound methods serialize and deserialize relevant for the component
dash_bio.SomeBioComponent(id="some-id", data=data)I think as a Dash app developer I would find that less intuitive compared to the slightly different import pandas as pd
import dash_bio
df = pd.read_csv("some.csv")
dash_bio.SomeBioComponent(id="some-id", data=df)Other benefits of the
|
|
@anders-kiaer Glad to have real users' feedback prior to release. A few points resonate with you.
However, I wanted to remind the original purpose that we wanted to allow complex data type to be transmitted via network requests (between Dash serving daemon and React client side) for initial layout & callbacks. Having that said, I feel #1786 could be a separate milestone than this one, and once we have this done, it might be much easier and quicker to implement #1786 for sure. After all, definitely something to be determined by code owners, so I'd like @chriddyp @alexcjohnson @jackparmer to make decision on this requirement. 🙏 |
|
Chatting with @chriddyp , we're going to keep the centralized direction that @workaholicpanda has started here. This won't prevent us from adding serializations for Dash Bio and other component suites, and could be a stepping stone to component-level serializations in the future. Right now, our main priority is to enable dataframe serialization to eliminate DataTable ceremony like this: With this PR, the above will be simplified to |
|
Remaining things
Blocking issues - |
akksi
left a comment
There was a problem hiding this comment.
Serialization doesn't respect the order of columns yet, e.g.
app.layout = html.Div(DataTable(DataFrame({'b': [2], 'a': [1]})))
Not really technical, but I think we can now use DataTable(df, instead of DataTable(data=df, in the examples.
* wip: async deserialization * feat: finish async deserialize, improve serialization with more additional/autofilled props * chore: minor refactor Co-authored-by: workaholicpanda <>
# Conflicts: # dash/dash-renderer/package-lock.json # dash/dash-renderer/package.json
This reverts commit f16df3d.
This reverts commit 9d072ab.
Idea
Provide non-lossy data exchange between server & renderer client in initial layout and callbacks, namely two-way serialization that allows either party sends any object then receive it back in its original type.
CHANGELOG
Added a serialization support of
pandas.DataFrameas the very first type, which will be followed by a few more types likenumpy.array,DateTime,Plotly figuresand more.Quick preview of how serialization work
- Layout
Before :
After :
- Callback
Before :
After :
CODE CHANGES
BREAKDOWNS OF TASKS
As described & outlined fully by @chriddyp
dashlibraryTESTINGS
Needs sophisticated tests to verify it's working with many various edge cases
DataFramefrom server to client (layout)DataFramefrom server to client (callbacks)DataFramefrom client to server (callbacks)