jmosbacher / pydantic-panel

Edit pydantic models with widgets from the awesome Panel package
MIT License
24 stars 3 forks source link

Take inspiration from streamlit-pydantic #20

Open MarcSkovMadsen opened 2 years ago

MarcSkovMadsen commented 2 years ago

streamlit-pydantic is an existing, more mature package. There might be a lot of things to learn and get inspiration from including the documentation and implementation.

The request is

jmosbacher commented 2 years ago

Actually what inspired me to write this package was frustration with streamlit-pydantic (so i should probably add them to the credits list :laughing: )

My goal for this implementation is to be the opposite of that implementation...

Im mostly joking of course, they do have a very nice README. I just dont yet feel comfortable advertising it so aggressively when its not yet stable and I dont have proper testing set up...

regarding the name change, as soon as the package is stable and feels like a "real" python package then id be happy to rename it and restart the version counter. P.S. The only reason I'm bumping the version currently is that I'm using it in another project that is deployed with pip so I tend to bump the version even after small changes if I happen to need them in the other package.

MarcSkovMadsen commented 2 years ago

Could you describe the issues with streamlit-pydantic that you are trying to solve? What should be done better here?

jmosbacher commented 2 years ago

Mostly the approach. They take the model .schema() result and they have a list of supported jsonschema specs, for each property they just go one by one with a huge if-else statement and test whether it matches that property type and if it does they call the appropriate function on the rendering class. There is no support for arbitrary types and configuration, only jsonschema stuff. My approach is to use the actual python type for dispatching what widget builder to use and to use the information in the actual ModelField instance inside the widget builder to construct a widget. The dispatch system is based on the python type-system and is completely overridable, so any user can add support for arbitrary python types or override the widget builder for a supported type or only for a specific sub-class.

For instance if you have a subclass of some type, eg

import pandas as pd
class DatetimeInterval(pd.Interval):
    # some logic asserting left and right are pd.Timestamp

and you wanted these intervals to be edited with a subclass of DatetimeRangePicker eg:

import pydantic_panel as pp
import panel as pn

class TimeIntervalEditor(DatetimeRangePicker):
    value = param.ClassSelector(DatetimeInterval, default=None)

    def _serialize_value(self, value):
        value = super()._serialize_value(value)
        if any([v is None for v in value]):
            return None
        return DatetimeInterval(left=value[0], right=value[1])

    def _deserialize_value(self, value):
        if isinstance(value, DatetimeInterval):
            value = value.left, value.right

        value = super()._deserialize_value(value)
        return value

    @param.depends('start', 'end', watch=True)
    def _update_value_bounds(self):
        pass

you would just do:

@pp.infer_widget.dispatch(precedence=2)
def infer_widget(value: DatetimeInterval, field: Optional[ModelField] = None, **kwargs):
    kwargs = clean_kwargs(TimeIntervalEditor, kwargs)
    return TimeIntervalEditor(value=value, **kwargs)

This kind of this is impossible in streamlit-pydantic just by nature of their design choices.