holoviz/param

Add a `metadata` slot

Opened this issue · 0 comments

Libraries like Pydantic, attrs, Traitlets and the built-in dataclasses all offer a way to attach metadata to their field/attributes with some more or less convenient API to retrieve them. Parameters in Param doesn't have a similar slot.

From experience I know I've needed that a few times and I've definitely abused the precedence slot to attach some data to Parameters.

Another motivation to add a metadata slot is related to Panel. The Param Pane offered by Panel is very practical to turn a Parameterized class into a viewable component, Panel maintaining a mapping between Parameter types and its widget types. However, sometimes one needs to configure a widget just a bit more and in this case Panel offers two approaches shown below: (1) pass additional widget configs to pn.Param or (2) use <WidgetClass>.from_param(<parameter>, ...) to instantiate a widget manually.

image

import param
import panel as pn

pn.extension()

class User(param.Parameterized):
    fullname = param.String()

user = User()

pn.Param(user.param, widgets={'fullname': {'width': 100}})  # 1

pn.widgets.TextInput.from_param(user.param.fullname, width=100)  # 2

I tend to use option (2) as there's often something custom I need to do (e.g. adding some text in between widgets, laying them out in a special way) that isn't made possible by (1), or at least not in a straightforward way. This makes the code quite verbose. Instead Panel could leverage the metadata slot to automatically pass widget kwargs collected from a special namespace (e.g. panel_kwargs) to .from_param() (that I believe is used in the implementation of pn.Param):

class User(param.Parameterized):
    fullname = param.String(metadata={'panel_kwargs': {'width': 100}})

user = User()
pn.Param(user.param)

This was recently discussed in holoviz/panel#5856.


metadata attribute in dataclasses, attrs, pydantic and traitlets:

image

Code

metadata = {'namespace': {'key1': 'value1'}}

## `dataclasses`

import dataclasses

@dataclasses.dataclass
class User:
    name: str = dataclasses.field(metadata=metadata)

user = User(name='bob')
dataclasses.fields(user)[0].metadata

## `attrs`

import attrs

@attrs.define
class User:
    name: str = attrs.field(metadata=metadata)

user = User(name='bob')
attrs.fields(User)[0].metadata

## `pydantic`

import pydantic

class User(pydantic.BaseModel):
    name: str = pydantic.Field(metadata=metadata)

user = User(name='bob')
user.model_fields['name'].json_schema_extra['metadata']

## `traitlets`

import traitlets

class User(traitlets.HasTraits):
    name = traitlets.Unicode().tag(**metadata)

user = User(name='bob')
user.trait_metadata('name', 'namespace')