Skip to content

Commit

Permalink
Merge pull request #27 from jmosbacher/pydantic2_support
Browse files Browse the repository at this point in the history
Support pydantic v2.0
  • Loading branch information
jmosbacher authored Feb 13, 2024
2 parents 4035dd0 + db9223b commit f072ca8
Show file tree
Hide file tree
Showing 7 changed files with 179 additions and 161 deletions.
2 changes: 1 addition & 1 deletion pydantic_panel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,4 +89,4 @@
__all__.append('NPArray')

except ImportError:
pass
pass
98 changes: 53 additions & 45 deletions pydantic_panel/dispatchers.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import param
import datetime
from typing import Dict, List, Any, Optional
from pydantic.fields import ModelField
import annotated_types

from typing import Any, Optional
from pydantic.fields import FieldInfo

try:
from typing import _LiteralGenericAlias
Expand Down Expand Up @@ -33,20 +35,20 @@


def clean_kwargs(obj: param.Parameterized,
kwargs: Dict[str,Any]) -> Dict[str,Any]:
kwargs: dict[str,Any]) -> dict[str,Any]:
'''Remove any kwargs that are not explicit parameters of obj.
'''
return {k: v for k, v in kwargs.items() if k in obj.param.params()}
return {k: v for k, v in kwargs.items() if k in obj.param.values()}


@dispatch
def infer_widget(value: Any, field: Optional[ModelField] = None, **kwargs) -> Widget:
def infer_widget(value: Any, field: Optional[FieldInfo] = None, **kwargs) -> Widget:
"""Fallback function when a more specific
function was not registered.
"""

if field is not None and type(field.outer_type_) == _LiteralGenericAlias:
options = list(field.outer_type_.__args__)
if field is not None and type(field.annotation) == _LiteralGenericAlias:
options = list(field.annotation.__args__)
if value not in options:
value = options[0]
options = kwargs.pop("options", options)
Expand All @@ -59,83 +61,89 @@ def infer_widget(value: Any, field: Optional[ModelField] = None, **kwargs) -> Wi


@dispatch
def infer_widget(value: Integral, field: Optional[ModelField] = None, **kwargs) -> Widget:
def infer_widget(value: Integral, field: Optional[FieldInfo] = None, **kwargs) -> Widget:
start = None
end = None
if field is not None:
if type(field.outer_type_) == _LiteralGenericAlias:
options = list(field.outer_type_.__args__)
if type(field.annotation) == _LiteralGenericAlias:
options = list(field.annotation.__args__)
if value not in options:
value = options[0]
options = kwargs.pop("options", options)
kwargs = clean_kwargs(Select, kwargs)
return Select(value=value, options=options, **kwargs)

start = getattr(field.field_info, "gt", None)
if start is not None:
start += 1
else:
start = getattr(field.field_info, "ge")

end = getattr(field.field_info, "lt", None)
if end is not None:
end -= 1
else:
end = getattr(field.field_info, "le", None)
for m in field.metadata:
if isinstance(m, annotated_types.Gt):
start = m.gt + 1
if isinstance(m, annotated_types.Ge):
start = m.ge
if isinstance(m, annotated_types.Lt):
end = m.lt - 1
if isinstance(m, annotated_types.Le):
end = m.le

kwargs = clean_kwargs(IntInput, kwargs)
return IntInput(value=value, start=start, end=end, **kwargs)


@dispatch
def infer_widget(value: Number, field: Optional[ModelField] = None, **kwargs) -> Widget:
def infer_widget(value: Number, field: Optional[FieldInfo] = None, **kwargs) -> Widget:
start = None
end = None
if field is not None:
if type(field.outer_type_) == _LiteralGenericAlias:
options = list(field.outer_type_.__args__)
if type(field.annotation) == _LiteralGenericAlias:
options = list(field.annotation.__args__)
if value not in options:
value = options[0]
options = kwargs.pop("options", options)
kwargs = clean_kwargs(Select, kwargs)
return Select(value=value, options=options, **kwargs)

start = getattr(field.field_info, "gt", None)
end = getattr(field.field_info, "lt", None)
for m in field.metadata:
if isinstance(m, annotated_types.Gt):
start = m.gt + 1
if isinstance(m, annotated_types.Ge):
start = m.ge
if isinstance(m, annotated_types.Lt):
end = m.lt - 1
if isinstance(m, annotated_types.Le):
end = m.le

kwargs = clean_kwargs(NumberInput, kwargs)
return NumberInput(value=value, start=start, end=end, **kwargs)


@dispatch
def infer_widget(value: bool, field: Optional[ModelField] = None, **kwargs) -> Widget:
def infer_widget(value: bool, field: Optional[FieldInfo] = None, **kwargs) -> Widget:
if value is None:
value = False
kwargs = clean_kwargs(Checkbox, kwargs)
return Checkbox(value=value, **kwargs)


@dispatch
def infer_widget(value: str, field: Optional[ModelField] = None, **kwargs) -> Widget:
def infer_widget(value: str, field: Optional[FieldInfo] = None, **kwargs) -> Widget:
min_length = kwargs.pop("min_length", None)
max_length = kwargs.pop("max_length", 100)

if field is not None:
if type(field.outer_type_) == _LiteralGenericAlias:
options = list(field.outer_type_.__args__)
if type(field.annotation) == _LiteralGenericAlias:
options = list(field.annotation.__args__)
if value not in options:
value = options[0]
options = kwargs.pop("options", options)
kwargs = clean_kwargs(Select, kwargs)
return Select(value=value, options=options, **kwargs)
max_length = field.field_info.max_length
min_length = field.field_info.min_length
for m in field.metadata:
if isinstance(m, annotated_types.MinLen):
min_length = m.min_length
if isinstance(m, annotated_types.MaxLen):
max_length = m.max_length

kwargs["min_length"] = min_length

if max_length is None:
kwargs = clean_kwargs(TextAreaInput, kwargs)
return TextAreaInput(value=value, **kwargs)

elif max_length < 100:
if max_length is not None and max_length < 100:
kwargs = clean_kwargs(TextInput, kwargs)
return TextInput(value=value, max_length=max_length, **kwargs)

Expand All @@ -144,9 +152,9 @@ def infer_widget(value: str, field: Optional[ModelField] = None, **kwargs) -> Wi


@dispatch
def infer_widget(value: List, field: Optional[ModelField] = None, **kwargs) -> Widget:
if field is not None and type(field.type_) == _LiteralGenericAlias:
options = list(field.type_.__args__)
def infer_widget(value: list, field: Optional[FieldInfo] = None, **kwargs) -> Widget:
if field is not None and type(field.annotation) == _LiteralGenericAlias:
options = list(field.annotation.__args__)
if value not in options:
value = []
kwargs = clean_kwargs(ListInput, kwargs)
Expand All @@ -158,36 +166,36 @@ def infer_widget(value: List, field: Optional[ModelField] = None, **kwargs) -> W


@dispatch
def infer_widget(value: Dict, field: Optional[ModelField] = None, **kwargs) -> Widget:
def infer_widget(value: dict, field: Optional[FieldInfo] = None, **kwargs) -> Widget:
kwargs = clean_kwargs(DictInput, kwargs)
return DictInput(value=value, **kwargs)


@dispatch
def infer_widget(value: tuple, field: Optional[ModelField] = None, **kwargs) -> Widget:
def infer_widget(value: tuple, field: Optional[FieldInfo] = None, **kwargs) -> Widget:
kwargs = clean_kwargs(TupleInput, kwargs)
return TupleInput(value=value, **kwargs)


@dispatch
def infer_widget(
value: datetime.datetime, field: Optional[ModelField] = None, **kwargs
value: datetime.datetime, field: Optional[FieldInfo] = None, **kwargs
):
kwargs = clean_kwargs(DatetimePicker, kwargs)
return DatetimePicker(value=value, **kwargs)


@dispatch
def infer_widget(
value: param.Parameterized, field: Optional[ModelField] = None, **kwargs
value: param.Parameterized, field: Optional[FieldInfo] = None, **kwargs
):
kwargs = clean_kwargs(Param, kwargs)
return Param(value, **kwargs)


@dispatch
def infer_widget(
value: List[param.Parameterized], field: Optional[ModelField] = None, **kwargs
value: list[param.Parameterized], field: Optional[FieldInfo] = None, **kwargs
):
kwargs = clean_kwargs(Param, kwargs)
return Column(*[Param(val, **kwargs) for val in value])
4 changes: 2 additions & 2 deletions pydantic_panel/numpy.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@

from typing import Optional
from plum import dispatch, parametric, type_of
from pydantic.fields import ModelField
from pydantic.fields import FieldInfo
from panel.widgets import Widget, ArrayInput

from .dispatchers import clean_kwargs


@dispatch
def infer_widget(
value: np.ndarray, field: Optional[ModelField] = None, **kwargs
value: np.ndarray, field: Optional[FieldInfo] = None, **kwargs
) -> Widget:
kwargs = clean_kwargs(ArrayInput, kwargs)
return ArrayInput(value=value, **kwargs)
Expand Down
10 changes: 5 additions & 5 deletions pydantic_panel/pandas.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@
import param
import pandas as pd

from pydantic.fields import ModelField
from pydantic.fields import FieldInfo
from panel.widgets import DatetimeRangePicker, EditableRangeSlider

from .dispatchers import clean_kwargs


class PandasTimeIntervalEditor(DatetimeRangePicker):
value = param.ClassSelector(pd.Interval, default=None)
value = param.ClassSelector(class_=pd.Interval, default=None)

def _serialize_value(self, value):
value = super()._serialize_value(value)
Expand All @@ -35,9 +35,9 @@ def _update_value_bounds(self):

class PandasIntervalEditor(EditableRangeSlider):

value = param.ClassSelector(pd.Interval, default=None)
value = param.ClassSelector(class_=pd.Interval, default=None)

value_throttled = param.ClassSelector(pd.Interval, default=None)
value_throttled = param.ClassSelector(class_=pd.Interval, default=None)

@param.depends("value", watch=True)
def _update_value(self):
Expand Down Expand Up @@ -83,7 +83,7 @@ class PandasIntegerIntervalEditor(PandasIntervalEditor):


@dispatch
def infer_widget(value: pd.Interval, field: Optional[ModelField] = None, **kwargs):
def infer_widget(value: pd.Interval, field: Optional[FieldInfo] = None, **kwargs):
if isinstance(value.left, pd.Timestamp) or isinstance(value.right, pd.Timestamp):
kwargs = clean_kwargs(PandasTimeIntervalEditor, kwargs)
return PandasTimeIntervalEditor(value=value, **kwargs)
Expand Down
3 changes: 1 addition & 2 deletions pydantic_panel/pane.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@

pyobject = object


class Pydantic(PaneBase):
"""The Pydantic pane wraps your Pydantic model into a Panel component.
Expand Down Expand Up @@ -58,7 +57,7 @@ def __init__(self, object=None, default_layout: Panel | None = None, **params):
params["default_layout"] = default_layout

pane_params = {
name: params[name] for name in Pydantic.param.params() if name in params
name: params[name] for name in Pydantic.param.values() if name in params
}

super().__init__(object, **pane_params)
Expand Down
Loading

0 comments on commit f072ca8

Please sign in to comment.