From 074937137718db362aa1bbdd19b67af09f812c9f Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 10 Jun 2022 10:57:15 +0200 Subject: [PATCH 01/23] Skip subprocess test on OSX (#3600) * Skip subprocess test on OSX * Disable another test --- panel/tests/widgets/test_terminal.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/panel/tests/widgets/test_terminal.py b/panel/tests/widgets/test_terminal.py index 11b3a31828..5766858c49 100644 --- a/panel/tests/widgets/test_terminal.py +++ b/panel/tests/widgets/test_terminal.py @@ -10,6 +10,7 @@ import panel as pn not_windows = pytest.mark.skipif(sys.platform == 'win32', reason="Does not work on Windows") +not_osx = pytest.mark.skipif(sys.platform == 'darwin', reason="Sometimes fails on OSX") def test_terminal_constructor(): @@ -38,6 +39,7 @@ def test_terminal(): @not_windows +@not_osx def test_subprocess(): args = "bash" terminal = pn.widgets.Terminal() @@ -62,6 +64,7 @@ def test_subprocess(): @not_windows +@not_osx def test_run_list_args(): terminal = pn.widgets.Terminal() subprocess = terminal.subprocess From a376451dab6d9fbb60e842100b6dd6ffca001568 Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 10 Jun 2022 16:54:54 +0200 Subject: [PATCH 02/23] Add types for all Widget and Pane class variables (#3604) * Add types for all Widget and Pane class variables * Fix tests --- panel/interact.py | 8 ++- panel/io/location.py | 5 +- panel/io/notifications.py | 3 +- panel/layout/accordion.py | 7 +- panel/layout/base.py | 68 ++++++++++--------- panel/layout/card.py | 17 +++-- panel/layout/grid.py | 16 ++++- panel/layout/tabs.py | 34 ++++++---- panel/pane/alert.py | 6 +- panel/pane/base.py | 28 ++++---- panel/pane/deckgl.py | 34 +++++++--- panel/pane/echarts.py | 22 ++++-- panel/pane/equation.py | 6 +- panel/pane/holoviews.py | 46 +++++++++---- panel/pane/idom.py | 15 +++- panel/pane/image.py | 37 ++++++---- panel/pane/ipywidget.py | 24 +++++-- panel/pane/markup.py | 77 +++++++++++++-------- panel/pane/media.py | 31 ++++++--- panel/pane/perspective.py | 24 +++++-- panel/pane/plot.py | 36 ++++++---- panel/pane/plotly.py | 24 +++++-- panel/pane/streamz.py | 18 +++-- panel/pane/vega.py | 22 ++++-- panel/pane/vtk/vtk.py | 52 ++++++++++---- panel/param.py | 47 +++++++++---- panel/reactive.py | 2 +- panel/viewable.py | 4 +- panel/widgets/ace.py | 16 ++++- panel/widgets/base.py | 25 +++---- panel/widgets/button.py | 38 +++++++---- panel/widgets/debugger.py | 18 ++--- panel/widgets/file_selector.py | 32 +++++---- panel/widgets/indicators.py | 59 ++++++++++------ panel/widgets/input.py | 106 +++++++++++++++++++---------- panel/widgets/misc.py | 13 ++-- panel/widgets/player.py | 15 ++-- panel/widgets/select.py | 35 ++++++---- panel/widgets/slider.py | 81 ++++++++++++++-------- panel/widgets/speech_to_text.py | 11 ++- panel/widgets/tables.py | 117 +++++++++++++++++++++----------- panel/widgets/text_to_speech.py | 11 ++- panel/widgets/texteditor.py | 14 +++- 43 files changed, 864 insertions(+), 440 deletions(-) diff --git a/panel/interact.py b/panel/interact.py index 4981c16010..092523e435 100644 --- a/panel/interact.py +++ b/panel/interact.py @@ -8,11 +8,14 @@ Copyright (c) Jupyter Development Team and PyViz Development Team. Distributed under the terms of the Modified BSD License. """ +from __future__ import annotations + import types from collections import OrderedDict from inspect import getcallargs from numbers import Integral, Real +from typing import TYPE_CHECKING try: # Python >= 3.3 from collections.abc import Iterable, Mapping @@ -42,6 +45,9 @@ TextInput, Widget, ) +if TYPE_CHECKING: + from bokeh.model import Model + def _get_min_max_value(min, max, value=None, step=None): """Return min, max, value given input values with possible None.""" @@ -208,7 +214,7 @@ def update_pane(change): watcher = widget.param.watch(update_pane, pname) self._callbacks.append(watcher) - def _cleanup(self, root): + def _cleanup(self, root: Model | None = None) -> None: self._inner_layout._cleanup(root) super()._cleanup(root) diff --git a/panel/io/location.py b/panel/io/location.py index 0bfa3b7817..b084b0e92f 100644 --- a/panel/io/location.py +++ b/panel/io/location.py @@ -79,7 +79,8 @@ def _get_model( return model def get_root( - self, doc: Optional[Document] = None, comm: Optional[Comm] = None, preprocess: bool = True + self, doc: Optional[Document] = None, comm: Optional[Comm] = None, + preprocess: bool = True ) -> 'Model': doc = init_doc(doc) root = self._get_model(doc, comm=comm) @@ -88,7 +89,7 @@ def get_root( self._documents[doc] = root return root - def _cleanup(self, root: Optional['Model']) -> None: + def _cleanup(self, root: Model | None = None) -> None: if root: if root.document in self._documents: del self._documents[root.document] diff --git a/panel/io/notifications.py b/panel/io/notifications.py index a5517c2f0e..d2283b445b 100644 --- a/panel/io/notifications.py +++ b/panel/io/notifications.py @@ -64,7 +64,8 @@ def __init__(self, **params): self._notification_watchers = {} def get_root( - self, doc: Optional['Document'] = None, comm: Optional['Comm'] = None, preprocess: bool = True + self, doc: Optional[Document] = None, comm: Optional[Comm] = None, + preprocess: bool = True ) -> 'Model': root = super().get_root(doc, comm, preprocess) self._documents[doc] = root diff --git a/panel/layout/accordion.py b/panel/layout/accordion.py index a2ccc9669c..85f5a17cde 100644 --- a/panel/layout/accordion.py +++ b/panel/layout/accordion.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import ClassVar, Mapping +from typing import TYPE_CHECKING, ClassVar, Mapping import param @@ -9,6 +9,9 @@ from .base import NamedListPanel from .card import Card +if TYPE_CHECKING: + from bokeh.model import Model + class Accordion(NamedListPanel): """ @@ -126,7 +129,7 @@ def _get_objects(self, model, old_objects, doc, root, comm=None): self._update_active() return new_models - def _cleanup(self, root): + def _cleanup(self, root: Model | None = None) -> None: for panel in self._panels.values(): panel._cleanup(root) super()._cleanup(root) diff --git a/panel/layout/base.py b/panel/layout/base.py index 959d719713..ba84121682 100644 --- a/panel/layout/base.py +++ b/panel/layout/base.py @@ -36,19 +36,19 @@ class Panel(Reactive): """ # Used internally to optimize updates - _batch_update: bool = False + _batch_update: ClassVar[bool] = False # Bokeh model used to render this Panel - _bokeh_model: Type['Model'] | None = None + _bokeh_model: ClassVar[Type[Model]] # Properties that should sync JS -> Python - _linked_props: List[str] = [] + _linked_props: ClassVar[List[str]] = [] # Parameters which require the preprocessors to be re-run - _preprocess_params: List[str] = [] + _preprocess_params: ClassVar[List[str]] = [] # Parameter -> Bokeh property renaming - _rename: Dict[str, str | None] = {'objects': 'children'} + _rename: ClassVar[Mapping[str, str | None]] = {'objects': 'children'} __abstract = True @@ -77,7 +77,7 @@ def __repr__(self, depth: int = 0, max_depth: int = 10) -> str: def _update_model( self, events: Dict[str, param.parameterized.Event], msg: Dict[str, Any], - root: 'Model', model: 'Model', doc: 'Document', comm: Optional['Comm'] + root: Model, model: Model, doc: Document, comm: Optional[Comm] ) -> None: msg = dict(msg) inverse = {v: k for k, v in self._rename.items() if v is not None} @@ -105,8 +105,8 @@ def _update_model( #---------------------------------------------------------------- def _get_objects( - self, model: 'Model', old_objects: List['Viewable'], doc: 'Document', - root: 'Model', comm: Optional['Comm'] = None + self, model: Model, old_objects: List[Viewable], doc: Document, + root: Model, comm: Optional[Comm] = None ): """ Returns new child models for the layout while reusing unchanged @@ -135,9 +135,9 @@ def _get_objects( return new_models def _get_model( - self, doc: Document, root: Optional['Model'] = None, - parent: Optional['Model'] = None, comm: Optional[Comm] = None - ) -> 'Model': + self, doc: Document, root: Optional[Model] = None, + parent: Optional[Model] = None, comm: Optional[Comm] = None + ) -> Model: if self._bokeh_model is None: raise ValueError(f'{type(self).__name__} did not define a _bokeh_model.') model = self._bokeh_model() @@ -180,15 +180,15 @@ class ListLike(param.Parameterized): objects = param.List(default=[], doc=""" The list of child objects that make up the layout.""") - _preprocess_params: List[str] = ['objects'] + _preprocess_params: ClassVar[List[str]] = ['objects'] - def __getitem__(self, index: int | slice) -> 'Viewable' | List['Viewable']: + def __getitem__(self, index: int | slice) -> Viewable | List[Viewable]: return self.objects[index] def __len__(self) -> int: return len(self.objects) - def __iter__(self) -> Iterator['Viewable']: + def __iter__(self) -> Iterator[Viewable]: for obj in self.objects: yield obj @@ -210,7 +210,7 @@ def __radd__(self, other: Iterable[Any]) -> 'ListLike': other = list(other) return self.clone(*(other+self.objects)) - def __contains__(self, obj: 'Viewable') -> bool: + def __contains__(self, obj: Viewable) -> bool: return obj in self.objects def __setitem__(self, index: int | slice, panes: Iterable[Any]) -> None: @@ -322,7 +322,7 @@ def insert(self, index: int, obj: Any) -> None: new_objects.insert(index, panel(obj)) self.objects = new_objects - def pop(self, index: int) -> 'Viewable': + def pop(self, index: int) -> Viewable: """ Pops an item from the layout by index. @@ -335,7 +335,7 @@ def pop(self, index: int) -> 'Viewable': self.objects = new_objects return obj - def remove(self, obj: 'Viewable') -> None: + def remove(self, obj: Viewable) -> None: """ Removes an object from the layout. @@ -361,9 +361,9 @@ class NamedListLike(param.Parameterized): objects = param.List(default=[], doc=""" The list of child objects that make up the layout.""") - _preprocess_params = ['objects'] + _preprocess_params: ClassVar[List[str]] = ['objects'] - def __init__(self, *items: List[Any, Tuple[str, Any]], **params: Any): + def __init__(self, *items: List[Any | Tuple[str, Any]], **params: Any): if 'objects' in params: if items: raise ValueError('%s objects should be supplied either ' @@ -416,13 +416,13 @@ def _update_active(self, *events: param.parameterized.Event) -> None: # Public API #---------------------------------------------------------------- - def __getitem__(self, index) -> 'Viewable' | List['Viewable']: + def __getitem__(self, index) -> Viewable | List[Viewable]: return self.objects[index] def __len__(self) -> int: return len(self.objects) - def __iter__(self) -> Iterator['Viewable']: + def __iter__(self) -> Iterator[Viewable]: for obj in self.objects: yield obj @@ -563,7 +563,7 @@ def insert(self, index: int, pane: Any) -> None: self._names.insert(index, new_name) self.objects = new_objects - def pop(self, index: int) -> 'Viewable': + def pop(self, index: int) -> Viewable: """ Pops an item from the tabs by index. @@ -577,7 +577,7 @@ def pop(self, index: int) -> 'Viewable': self.objects = new_objects return obj - def remove(self, pane: 'Viewable') -> None: + def remove(self, pane: Viewable) -> None: """ Removes an object from the tabs. @@ -616,7 +616,7 @@ class ListPanel(ListLike, Panel): Whether to add scrollbars if the content overflows the size of the container.""") - _source_transforms: Mapping[str, str | None] = {'scroll': None} + _source_transforms: ClassVar[Mapping[str, str | None]] = {'scroll': None} __abstract = True @@ -641,7 +641,7 @@ def _process_param_change(self, params: Dict[str, Any]) -> Dict[str, Any]: params['css_classes'] = css_classes return super()._process_param_change(params) - def _cleanup(self, root: 'Model' | None): + def _cleanup(self, root: Model | None = None) -> None: if root is not None and root.ref['id'] in state._fake_roots: state._fake_roots.remove(root.ref['id']) super()._cleanup(root) @@ -663,7 +663,7 @@ class NamedListPanel(NamedListLike, Panel): Whether to add scrollbars if the content overflows the size of the container.""") - _source_transforms: Dict[str, str | None] = {'scroll': None} + _source_transforms: ClassVar[Mapping[str, str | None]] = {'scroll': None} __abstract = True @@ -676,7 +676,7 @@ def _process_param_change(self, params: Dict[str, Any]) -> Dict[str, Any]: params['css_classes'] = css_classes return super()._process_param_change(params) - def _cleanup(self, root: 'Model' | None) -> None: + def _cleanup(self, root: Model | None = None) -> None: if root is not None and root.ref['id'] in state._fake_roots: state._fake_roots.remove(root.ref['id']) super()._cleanup(root) @@ -702,7 +702,7 @@ class Row(ListPanel): col_sizing = param.Parameter() - _bokeh_model: Type['Model'] = BkRow + _bokeh_model: ClassVar[Type[Model]] = BkRow _rename: ClassVar[Mapping[str, str | None]] = dict(ListPanel._rename, col_sizing='cols') @@ -725,7 +725,7 @@ class Column(ListPanel): row_sizing = param.Parameter() - _bokeh_model: Type['Model'] = BkColumn + _bokeh_model: ClassVar[Type[Model]] = BkColumn _rename: ClassVar[Mapping[str, str | None]] = dict(ListPanel._rename, row_sizing='rows') @@ -764,12 +764,16 @@ class WidgetBox(ListPanel): be specified as a two-tuple of the form (vertical, horizontal) or a four-tuple (top, right, bottom, left).""") - _source_transforms = {'disabled': None, 'horizontal': None} + _source_transforms: ClassVar[Mapping[str, str | None]] = { + 'disabled': None, 'horizontal': None + } - _rename: ClassVar[Mapping[str, str | None]] = {'objects': 'children', 'horizontal': None} + _rename: ClassVar[Mapping[str, str | None]] = { + 'objects': 'children', 'horizontal': None + } @property - def _bokeh_model(self) -> Type['Model']: # type: ignore + def _bokeh_model(self) -> Type[Model]: # type: ignore return BkRow if self.horizontal else BkColumn @param.depends('disabled', 'objects', watch=True) diff --git a/panel/layout/card.py b/panel/layout/card.py index 3d995059fa..7556ec4b8b 100644 --- a/panel/layout/card.py +++ b/panel/layout/card.py @@ -1,12 +1,17 @@ from __future__ import annotations -from typing import ClassVar, Mapping +from typing import ( + TYPE_CHECKING, ClassVar, List, Mapping, Type, +) import param from ..models import Card as BkCard from .base import Column, ListPanel, Row +if TYPE_CHECKING: + from bokeh.model import Model + class Card(Column): """ @@ -63,11 +68,13 @@ class Card(Column): A title to be displayed in the Card header, will be overridden by the header if defined.""") - _bokeh_model = BkCard + _bokeh_model: ClassVar[Type[Model]] = BkCard - _linked_props = ['collapsed'] + _linked_props: ClassVar[List[str]] = ['collapsed'] - _rename: ClassVar[Mapping[str, str | None]] = dict(Column._rename, title=None, header=None, title_css_classes=None) + _rename: ClassVar[Mapping[str, str | None]] = dict( + Column._rename, title=None, header=None, title_css_classes=None + ) def __init__(self, *objects, **params): self._header_layout = Row(css_classes=['card-header-row'], @@ -77,7 +84,7 @@ def __init__(self, *objects, **params): self.param.watch(self._update_header, ['title', 'header', 'title_css_classes']) self._update_header() - def _cleanup(self, root): + def _cleanup(self, root: Model | None = None) -> None: super()._cleanup(root) self._header_layout._cleanup(root) diff --git a/panel/layout/grid.py b/panel/layout/grid.py index 2946aa3d1e..aae90214eb 100644 --- a/panel/layout/grid.py +++ b/panel/layout/grid.py @@ -7,7 +7,9 @@ from collections import OrderedDict, namedtuple from functools import partial -from typing import ClassVar, Mapping +from typing import ( + TYPE_CHECKING, Any, ClassVar, Dict, Mapping, Optional, +) import numpy as np import param @@ -19,6 +21,11 @@ ListPanel, Panel, _col, _row, ) +if TYPE_CHECKING: + from bokeh.document import Document + from bokeh.model import Model + from pyviz_comms import Comm + class GridBox(ListPanel): """ @@ -155,7 +162,10 @@ def _get_model(self, doc, root=None, parent=None, comm=None): self._link_props(model, self._linked_props, doc, root, comm) return model - def _update_model(self, events, msg, root, model, doc, comm=None): + def _update_model( + self, events: Dict[str, param.parameterized.Event], msg: Dict[str, Any], + root: Model, model: Model, doc: Document, comm: Optional[Comm] + ) -> None: from ..io import state msg = dict(msg) @@ -327,7 +337,7 @@ def _object_grid(self): grid[y, x] = {((y0, x0, y1, x1), obj)} return grid - def _cleanup(self, root): + def _cleanup(self, root: Model | None = None) -> None: super()._cleanup(root) for p in self.objects.values(): p._cleanup(root) diff --git a/panel/layout/tabs.py b/panel/layout/tabs.py index f47b203475..61c15adf71 100644 --- a/panel/layout/tabs.py +++ b/panel/layout/tabs.py @@ -4,7 +4,9 @@ from __future__ import annotations from collections import defaultdict -from typing import ClassVar, Mapping +from typing import ( + TYPE_CHECKING, ClassVar, List, Mapping, Type, +) import param @@ -14,6 +16,9 @@ from ..viewable import Layoutable from .base import NamedListPanel +if TYPE_CHECKING: + from bokeh.model import Model + class Tabs(NamedListPanel): """ @@ -48,21 +53,25 @@ class Tabs(NamedListPanel): width = param.Integer(default=None, bounds=(0, None)) - _bokeh_model = BkTabs + _bokeh_model: ClassVar[Type[Model]] = BkTabs - _js_transforms = {'tabs': """ + _js_transforms: ClassVar[Mapping[str, str]] = {'tabs': """ var ids = []; for (var t of value) {{ ids.push(t.id) }}; var value = ids; """} - _linked_props = ['active', 'tabs'] + _linked_props: ClassVar[List[str]] = ['active', 'tabs'] - _manual_params = ['closable'] + _manual_params: ClassVar[List[str]] = ['closable'] - _rename: ClassVar[Mapping[str, str | None]] = {'name': None, 'objects': 'tabs', 'dynamic': None} + _rename: ClassVar[Mapping[str, str | None]] = { + 'name': None, 'objects': 'tabs', 'dynamic': None + } - _source_transforms = {'dynamic': None, 'objects': None} + _source_transforms: ClassVar[Mapping[str, str | None]] = { + 'dynamic': None, 'objects': None + } def __init__(self, *objects, **params): super().__init__(*objects, **params) @@ -74,12 +83,13 @@ def _update_names(self, event): self.param.active.bounds = (0, len(event.new)-1) super()._update_names(event) - def _cleanup(self, root): + def _cleanup(self, root: Model | None = None) -> None: super()._cleanup(root) - if root.ref['id'] in self._panels: - del self._panels[root.ref['id']] - if root.ref['id'] in self._rendered: - del self._rendered[root.ref['id']] + if root: + if root.ref['id'] in self._panels: + del self._panels[root.ref['id']] + if root.ref['id'] in self._rendered: + del self._rendered[root.ref['id']] @property def _preprocess_params(self): diff --git a/panel/pane/alert.py b/panel/pane/alert.py index 781c9f4858..28f4a8a4d0 100644 --- a/panel/pane/alert.py +++ b/panel/pane/alert.py @@ -5,7 +5,7 @@ """ from __future__ import annotations -from typing import ClassVar, Mapping +from typing import Any, ClassVar, Mapping import param @@ -28,12 +28,12 @@ class Alert(Markdown): alert_type = param.ObjectSelector("primary", objects=ALERT_TYPES) - priority = 0 + priority: ClassVar[float | bool | None] = 0 _rename: ClassVar[Mapping[str, str | None]] = dict(Markdown._rename, alert_type=None) @classmethod - def applies(cls, obj): + def applies(cls, obj: Any) -> float | bool | None: priority = Markdown.applies(obj) return 0 if priority else False diff --git a/panel/pane/base.py b/panel/pane/base.py index 598ec1d61e..144a18ff0a 100644 --- a/panel/pane/base.py +++ b/panel/pane/base.py @@ -8,7 +8,7 @@ from functools import partial from typing import ( - TYPE_CHECKING, Any, Callable, List, Optional, Type, TypeVar, + TYPE_CHECKING, Any, Callable, ClassVar, List, Optional, Type, TypeVar, ) import param @@ -121,19 +121,19 @@ class PaneBase(Reactive): # numerical priority is selected. The default is an intermediate value. # If set to None, applies method will be called to get a priority # value for a specific object type. - priority: float | bool | None = 0.5 + priority: ClassVar[float | bool | None] = 0.5 # Whether applies requires full set of keywords - _applies_kw = False + _applies_kw: ClassVar[bool] = False # Whether the Pane layout can be safely unpacked - _unpack: bool = True + _unpack: ClassVar[bool] = True # Declares whether Pane supports updates to the Bokeh model - _updates: bool = False + _updates: ClassVar[bool] = False # List of parameters that trigger a rerender of the Bokeh model - _rerender_params: List[str] = ['object'] + _rerender_params: ClassVar[List[str]] = ['object'] __abstract = True @@ -179,7 +179,7 @@ def _synced_params(self) -> List[str]: return [p for p in self.param if p not in ignored_params] def _update_object( - self, ref: str, doc: 'Document', root: 'Model', parent: 'Model', comm: Optional['Comm'] + self, ref: str, doc: 'Document', root: Model, parent: Model, comm: Optional[Comm] ) -> None: old_model = self._models[ref][0] if self._updates: @@ -246,7 +246,7 @@ def _update_pane(self, *events) -> None: else: cb() - def _update(self, ref: Optional[str] = None, model: Optional['Model'] = None) -> None: + def _update(self, ref: str, model: Model) -> None: """ If _updates=True this method is used to update an existing Bokeh model instead of replacing the model entirely. The @@ -290,9 +290,9 @@ def clone(self: T, object: Optional[Any] = None, **params) -> T: return type(self)(object, **params) def get_root( - self, doc: Optional['Document'] = None, comm: Optional['Comm'] = None, + self, doc: Optional[Document] = None, comm: Optional[Comm] = None, preprocess: bool = True - ) -> 'Model': + ) -> Model: """ Returns the root model and applies pre-processing hooks @@ -460,9 +460,9 @@ def _update_inner(self, new_object: Any) -> None: self._internal = internal def _get_model( - self, doc: 'Document', root: Optional['Model'] = None, - parent: Optional['Model'] = None, comm: Optional['Comm'] = None - ) -> 'Model': + self, doc: Document, root: Optional[Model] = None, + parent: Optional[Model] = None, comm: Optional[Comm] = None + ) -> Model: if root: ref = root.ref['id'] if ref in self._models: @@ -473,7 +473,7 @@ def _get_model( self._models[ref] = (model, parent) return model - def _cleanup(self, root: 'Model' | None = None) -> None: + def _cleanup(self, root: Model | None = None) -> None: self._inner_layout._cleanup(root) super()._cleanup(root) diff --git a/panel/pane/deckgl.py b/panel/pane/deckgl.py index 93104015d8..bb8eb7bbcf 100644 --- a/panel/pane/deckgl.py +++ b/panel/pane/deckgl.py @@ -7,7 +7,9 @@ import json from collections import defaultdict -from typing import ClassVar, Mapping +from typing import ( + TYPE_CHECKING, Any, ClassVar, Mapping, Optional, +) import numpy as np import param @@ -19,9 +21,15 @@ from ..viewable import Layoutable from .base import PaneBase +if TYPE_CHECKING: + from bokeh.document import Document + from bokeh.model import Model + from pyviz_comms import Comm + def lower_camel_case_keys(attrs): - """Makes all the keys in a dictionary camel-cased and lower-case + """ + Makes all the keys in a dictionary camel-cased and lower-case Parameters ---------- @@ -35,8 +43,9 @@ def lower_camel_case_keys(attrs): attrs[camel_key] = attrs.pop(snake_key) -def to_camel_case(snake_case): - """Makes a snake case string into a camel case one +def to_camel_case(snake_case: str) -> str: + """ + Makes a snake case string into a camel case one Parameters ----------- @@ -54,7 +63,7 @@ def to_camel_case(snake_case): return output_str -def lower_first_letter(s): +def lower_first_letter(s: str) -> str: return s[:1].lower() + s[1:] if s else '' @@ -114,12 +123,12 @@ class DeckGL(PaneBase): 'view_state': 'viewState', 'tooltips': 'tooltip' } - _updates = True + _updates: ClassVar[bool] = True - priority = None + priority: ClassVar[float | bool | None] = None @classmethod - def applies(cls, obj): + def applies(cls, obj: Any) -> float | bool | None: if (hasattr(obj, "to_json") and hasattr(obj, "mapbox_key") and hasattr(obj, "deck_widget")): return 0.8 @@ -127,7 +136,7 @@ def applies(cls, obj): return 0 return False - def _get_properties(self, layout=True): + def _get_properties(self, layout: bool = True): if self.object is None: data, mapbox_api_key, tooltip = {}, self.mapbox_api_key, self.tooltips elif isinstance(self.object, (str, dict)): @@ -213,7 +222,10 @@ def _update_sources(cls, json_data, sources): sources.append(cds) layer['data'] = sources.index(cds) - def _get_model(self, doc, root=None, parent=None, comm=None): + def _get_model( + self, doc: Document, root: Optional[Model] = None, + parent: Optional[Model] = None, comm: Optional[Comm] = None + ) -> Model: DeckGLPlot = lazy_load( 'panel.models.deckgl', 'DeckGLPlot', isinstance(comm, JupyterComm), root ) @@ -228,7 +240,7 @@ def _get_model(self, doc, root=None, parent=None, comm=None): self._models[root.ref["id"]] = (model, parent) return model - def _update(self, ref=None, model=None): + def _update(self, ref: str, model: Model) -> None: data, properties = self._get_properties(layout=False) self._update_sources(data, model.data_sources) properties['data'] = data diff --git a/panel/pane/echarts.py b/panel/pane/echarts.py index 8a0d0c5324..ca4bec5077 100644 --- a/panel/pane/echarts.py +++ b/panel/pane/echarts.py @@ -3,7 +3,9 @@ import json import sys -from typing import ClassVar, Mapping +from typing import ( + TYPE_CHECKING, Any, ClassVar, List, Mapping, Optional, +) import param @@ -12,6 +14,11 @@ from ..util import lazy_load from .base import PaneBase +if TYPE_CHECKING: + from bokeh.document import Document + from bokeh.model import Model + from pyviz_comms import Comm + class ECharts(PaneBase): """ @@ -34,16 +41,16 @@ class ECharts(PaneBase): theme = param.ObjectSelector(default="default", objects=["default", "light", "dark"], doc=""" Theme to apply to plots.""") - priority = None + priority: ClassVar[float | bool | None] = None _rename: ClassVar[Mapping[str, str | None]] = {"object": "data"} - _rerender_params = [] + _rerender_params: ClassVar[List[str]] = [] - _updates = True + _updates: ClassVar[bool] = True @classmethod - def applies(cls, obj, **params): + def applies(cls, obj: Any, **params) -> float | bool | None: if isinstance(obj, dict): return 0 elif "pyecharts." in repr(obj.__class__): @@ -60,7 +67,10 @@ def _get_dimensions(cls, props): else: props['sizing_mode'] = 'fixed' - def _get_model(self, doc, root=None, parent=None, comm=None): + def _get_model( + self, doc: Document, root: Optional[Model] = None, + parent: Optional[Model] = None, comm: Optional[Comm] = None + ) -> Model: ECharts = lazy_load('panel.models.echarts', 'ECharts', isinstance(comm, JupyterComm), root) props = self._get_echart_dict(self.object) props.update(self._process_param_change(self._init_params())) diff --git a/panel/pane/equation.py b/panel/pane/equation.py index 7e2cb37898..527c4df125 100644 --- a/panel/pane/equation.py +++ b/panel/pane/equation.py @@ -6,7 +6,7 @@ import sys -from typing import ClassVar, Mapping +from typing import Any, ClassVar, Mapping import param @@ -49,12 +49,12 @@ class LaTeX(DivPaneBase): The JS renderer used to render the LaTeX expression.""") # Priority is dependent on the data type - priority = None + priority: ClassVar[float | bool | None] = None _rename: ClassVar[Mapping[str, str | None]] = {"renderer": None} @classmethod - def applies(cls, obj): + def applies(cls, obj: Any) -> float | bool | None: if is_sympy_expr(obj) or hasattr(obj, '_repr_latex_'): return 0.05 elif isinstance(obj, str): diff --git a/panel/pane/holoviews.py b/panel/pane/holoviews.py index 7439beb674..bfb25ed428 100644 --- a/panel/pane/holoviews.py +++ b/panel/pane/holoviews.py @@ -8,7 +8,9 @@ from collections import OrderedDict, defaultdict from functools import partial -from typing import ClassVar, Mapping +from typing import ( + TYPE_CHECKING, Any, ClassVar, Mapping, Optional, Type, +) import param @@ -26,6 +28,11 @@ from .plot import Bokeh, Matplotlib from .plotly import Plotly +if TYPE_CHECKING: + from bokeh.document import Document + from bokeh.model import Model + from pyviz_comms import Comm + class HoloViews(PaneBase): """ @@ -82,9 +89,11 @@ class HoloViews(PaneBase): A mapping from dimension name to a widget instance which will be used to override the default widgets.""") - priority = 0.8 + priority: ClassVar[float | bool | None] = 0.8 - _panes = {'bokeh': Bokeh, 'matplotlib': Matplotlib, 'plotly': Plotly} + _panes: ClassVar[Mapping[str, Type[PaneBase]]] = { + 'bokeh': Bokeh, 'matplotlib': Matplotlib, 'plotly': Plotly + } _rename: ClassVar[Mapping[str, str | None]] = { 'backend': None, 'center': None, 'linked_axes': None, @@ -234,7 +243,10 @@ def _widget_callback(self, event): # Model API #---------------------------------------------------------------- - def _get_model(self, doc, root=None, parent=None, comm=None): + def _get_model( + self, doc: Document, root: Optional[Model] = None, + parent: Optional[Model] = None, comm: Optional[Comm] = None + ) -> Model: from holoviews.plotting.plot import Plot if root is None: return self.get_root(doc, comm) @@ -329,16 +341,17 @@ def _render(self, doc, comm, root): return renderer.get_plot(self.object, **kwargs) - def _cleanup(self, root): + def _cleanup(self, root: Model | None = None) -> None: """ Traverses HoloViews object to find and clean up any streams connected to existing plots. """ - old_plot, old_pane = self._plots.pop(root.ref['id'], (None, None)) - if old_plot: - old_plot.cleanup() - if old_pane: - old_pane._cleanup(root) + if root: + old_plot, old_pane = self._plots.pop(root.ref['id'], (None, None)) + if old_plot: + old_plot.cleanup() + if old_pane: + old_pane._cleanup(root) super()._cleanup(root) #---------------------------------------------------------------- @@ -346,7 +359,7 @@ def _cleanup(self, root): #---------------------------------------------------------------- @classmethod - def applies(cls, obj): + def applies(cls, obj: Any) -> float | bool | None: if 'holoviews' not in sys.modules: return False from holoviews.core.dimension import Dimensioned @@ -499,7 +512,7 @@ def widgets_from_dimensions(cls, object, widget_types=None, widgets_type='indivi class Interactive(PaneBase): - priority = None + priority: ClassVar[float | bool | None] = None def __init__(self, object=None, **params): super().__init__(object, **params) @@ -507,7 +520,7 @@ def __init__(self, object=None, **params): self.param.watch(self._update_layout_properties, list(Layoutable.param)) @classmethod - def applies(cls, object): + def applies(cls, object: Any) -> float | bool | None: if 'hvplot.interactive' not in sys.modules: return False from hvplot.interactive import Interactive @@ -528,7 +541,10 @@ def _update_layout_properties(self, *events): return self._layout_panel.param.update(**{e.name: e.new for e in events}) - def _get_model(self, doc, root=None, parent=None, comm=None): + def _get_model( + self, doc: Document, root: Optional[Model] = None, + parent: Optional[Model] = None, comm: Optional[Comm] = None + ) -> Model: if root is None: return self.get_root(doc, comm) if self._layout_panel is None: @@ -540,7 +556,7 @@ def _get_model(self, doc, root=None, parent=None, comm=None): self._models[root.ref['id']] = (model, parent) return model - def _cleanup(self, root): + def _cleanup(self, root: Model | None = None) -> None: if self._layout_panel is not None: self._layout_panel._cleanup(root) super()._cleanup(root) diff --git a/panel/pane/idom.py b/panel/pane/idom.py index d2551ad7fb..86c06ff009 100644 --- a/panel/pane/idom.py +++ b/panel/pane/idom.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import asyncio import shutil import sys @@ -5,6 +7,7 @@ from functools import partial from queue import Queue as SyncQueue from threading import Thread +from typing import TYPE_CHECKING, Optional from packaging.version import Version @@ -14,6 +17,11 @@ from ..models import IDOM as _BkIDOM from .base import PaneBase +if TYPE_CHECKING: + from bokeh.document import Document + from bokeh.model import Model + from pyviz_comms import Comm + _IDOM_MIN_VER = "0.23" _IDOM_MAX_VER = "0.24" @@ -89,7 +97,10 @@ def _setup(self): self._idom_layout = Layout(self.object()) self._idom_loop = _spawn_threaded_event_loop(self._idom_layout_render_loop()) - def _get_model(self, doc, root=None, parent=None, comm=None): + def _get_model( + self, doc: Document, root: Optional[Model] = None, + parent: Optional[Model] = None, comm: Optional[Comm] = None + ) -> Model: from idom.config import IDOM_CLIENT_IMPORT_SOURCE_URL from idom.core.layout import LayoutUpdate @@ -118,7 +129,7 @@ def _get_model(self, doc, root=None, parent=None, comm=None): self._models[root.ref['id']] = (model, parent) return model - def _cleanup(self, root): + def _cleanup(self, root: Model | None = None) -> None: super()._cleanup(root) if not self._models: # Clean up loop when no views are shown diff --git a/panel/pane/image.py b/panel/pane/image.py index 259bfb99bd..a88649f9db 100644 --- a/panel/pane/image.py +++ b/panel/pane/image.py @@ -2,10 +2,15 @@ Contains Image panes including renderers for PNG, SVG, GIF and JPG file types. """ +from __future__ import annotations + import base64 from io import BytesIO from pathlib import PurePath +from typing import ( + Any, ClassVar, List, Mapping, +) import param @@ -18,7 +23,9 @@ class FileBase(DivPaneBase): embed = param.Boolean(default=True, doc=""" Whether to embed the file as base64.""") - _rerender_params = ['embed', 'object', 'style', 'width', 'height'] + _rerender_params: ClassVar[List[str]] = [ + 'embed', 'object', 'style', 'width', 'height' + ] __abstract = True @@ -34,7 +41,7 @@ def _type_error(self, object): super()._type_error(object) @classmethod - def applies(cls, obj): + def applies(cls, obj: Any) -> float | bool | None: filetype = cls.filetype if hasattr(obj, '_repr_{}_'.format(filetype)): return True @@ -98,11 +105,15 @@ class ImageBase(FileBase): A link URL to make the image clickable and link to some other website.""") - filetype = 'None' + filetype: ClassVar[str] = 'None' - _rerender_params = ['alt_text', 'link_url', 'embed', 'object', 'style', 'width', 'height'] + _rerender_params: ClassVar[List[str]] = [ + 'alt_text', 'link_url', 'embed', 'object', 'style', 'width', 'height' + ] - _target_transforms = {'object': """''"""} + _target_transforms: ClassVar[Mapping[str, str | None]] = { + 'object': """''""" + } __abstract = True @@ -181,7 +192,7 @@ class PNG(ImageBase): ... ) """ - filetype = 'png' + filetype: ClassVar[str] = 'png' @classmethod def _imgshape(cls, data): @@ -207,7 +218,7 @@ class GIF(ImageBase): ... ) """ - filetype = 'gif' + filetype: ClassVar[str] = 'gif' @classmethod def _imgshape(cls, data): @@ -233,7 +244,7 @@ class ICO(ImageBase): ... """ - filetype = 'ico' + filetype: ClassVar[str] = 'ico' @classmethod def _imgshape(cls, data): @@ -259,7 +270,7 @@ class JPG(ImageBase): ... ) """ - filetype = 'jpg' + filetype: ClassVar[str] = 'jpg' @classmethod def _imgshape(cls, data): @@ -301,12 +312,12 @@ class SVG(ImageBase): Whether to enable base64 encoding of the SVG, base64 encoded SVGs do not support links.""") - filetype = 'svg' + filetype: ClassVar[str] = 'svg' - _rerender_params = ImageBase._rerender_params + ['encode'] + _rerender_params: ClassVar[List[str]] = ImageBase._rerender_params + ['encode'] @classmethod - def applies(cls, obj): + def applies(cls, obj: Any) -> float | bool | None: return (super().applies(obj) or (isinstance(obj, str) and obj.lstrip().startswith('>> IPyWidget(some_ipywidget) """ - priority = 0.6 + priority: ClassVar[float | bool | None] = 0.6 @classmethod - def applies(cls, obj): + def applies(cls, obj: Any) -> float | bool | None: return (hasattr(obj, 'traits') and hasattr(obj, 'get_manager_state') and hasattr(obj, 'comm')) def _get_ipywidget(self, obj, doc, root, comm, **kwargs): @@ -55,7 +66,10 @@ def _get_ipywidget(self, obj, doc, root, comm, **kwargs): model = IPyWidget(widget=obj, **kwargs) return model - def _get_model(self, doc, root=None, parent=None, comm=None): + def _get_model( + self, doc: Document, root: Optional[Model] = None, + parent: Optional[Model] = None, comm: Optional[Comm] = None + ) -> Model: if root is None: return self.get_root(doc, comm) kwargs = self._process_param_change(self._init_params()) @@ -70,8 +84,8 @@ class IPyLeaflet(IPyWidget): 'fixed', 'stretch_width', 'stretch_height', 'stretch_both', 'scale_width', 'scale_height', 'scale_both', None]) - priority = 0.7 + priority: float | bool | None = 0.7 @classmethod - def applies(cls, obj): + def applies(cls, obj: Any) -> float | bool | None: return IPyWidget.applies(obj) and obj._view_module == 'jupyter-leaflet' diff --git a/panel/pane/markup.py b/panel/pane/markup.py index 661626e7ff..b815658f66 100644 --- a/panel/pane/markup.py +++ b/panel/pane/markup.py @@ -7,7 +7,9 @@ import json import textwrap -from typing import ClassVar, Mapping +from typing import ( + TYPE_CHECKING, Any, ClassVar, List, Mapping, Optional, Type, +) import param @@ -16,6 +18,11 @@ from ..viewable import Layoutable from .base import PaneBase +if TYPE_CHECKING: + from bokeh.document import Document + from bokeh.model import Model + from pyviz_comms import Comm + class DivPaneBase(PaneBase): """ @@ -27,11 +34,11 @@ class DivPaneBase(PaneBase): style = param.Dict(default=None, doc=""" Dictionary of CSS property:value pairs to apply to this Div.""") - _bokeh_model = _BkHTML + _bokeh_model: ClassVar[Model] = _BkHTML _rename: ClassVar[Mapping[str, str | None]] = {'object': 'text'} - _updates = True + _updates: ClassVar[bool] = True __abstract = True @@ -39,14 +46,17 @@ def _get_properties(self): return {p : getattr(self, p) for p in list(Layoutable.param) + ['style'] if getattr(self, p) is not None} - def _get_model(self, doc, root=None, parent=None, comm=None): + def _get_model( + self, doc: Document, root: Optional[Model] = None, + parent: Optional[Model] = None, comm: Optional[Comm] = None + ) -> Model: model = self._bokeh_model(**self._get_properties()) if root is None: root = model self._models[root.ref['id']] = (model, parent) return model - def _update(self, ref=None, model=None): + def _update(self, ref: str, model: Model) -> None: model.update(**self._get_properties()) @@ -72,10 +82,10 @@ class HTML(DivPaneBase): strings escaped with $$ delimiters.""") # Priority is dependent on the data type - priority = None + priority: ClassVar[float | bool | None] = None @classmethod - def applies(cls, obj): + def applies(cls, obj: Any) -> float | bool | None: module, name = getattr(obj, '__module__', ''), type(obj).__name__ if ((any(m in module for m in ('pandas', 'dask')) and name in ('DataFrame', 'Series')) or hasattr(obj, '_repr_html_')): @@ -173,9 +183,9 @@ class DataFrame(HTML): _object = param.Parameter(default=None, doc="""Hidden parameter.""") - _dask_params = ['max_rows'] + _dask_params: ClassVar[List[str]] = ['max_rows'] - _rerender_params = [ + _rerender_params: ClassVar[List[str]] = [ 'object', '_object', 'bold_rows', 'border', 'classes', 'col_space', 'decimal', 'escape', 'float_format', 'formatters', 'header', 'index', 'index_names', 'justify', 'max_rows', @@ -189,7 +199,7 @@ def __init__(self, object=None, **params): self._setup_stream() @classmethod - def applies(cls, obj): + def applies(cls, obj: Any) -> float | bool | None: module = getattr(obj, '__module__', '') name = type(obj).__name__ if (any(m in module for m in ('pandas', 'dask', 'streamz')) and @@ -211,13 +221,16 @@ def _setup_stream(self): self._stream = self.object.stream.latest().rate_limit(0.5).gather() self._stream.sink(self._set_object) - def _get_model(self, doc, root=None, parent=None, comm=None): + def _get_model( + self, doc: Document, root: Optional[Model] = None, + parent: Optional[Model] = None, comm: Optional[Comm] = None + ) -> Model: model = super()._get_model(doc, root, parent, comm) self._setup_stream() return model - def _cleanup(self, model): - super()._cleanup(model) + def _cleanup(self, root: Model | None = None) -> None: + super()._cleanup(root) if not self._models and self._stream: self._stream.destroy() self._stream = None @@ -267,14 +280,16 @@ class Str(DivPaneBase): ... ) """ - priority = 0 + priority: ClassVar[float | bool | None] = 0 - _target_transforms = {'object': """JSON.stringify(value).replace(/,/g, ", ").replace(/:/g, ": ")"""} + _bokeh_model: ClassVar[Type[Model]] = _BkHTML - _bokeh_model = _BkHTML + _target_transforms: ClassVar[Mapping[str, str | None]] = { + 'object': """JSON.stringify(value).replace(/,/g, ", ").replace(/:/g, ": ")""" + } @classmethod - def applies(cls, obj): + def applies(cls, obj: Any) -> bool: return True def _get_properties(self): @@ -312,14 +327,16 @@ class Markdown(DivPaneBase): Markdown extension to apply when transforming markup.""") # Priority depends on the data type - priority = None + priority: ClassVar[float | bool | None] = None - _target_transforms = {'object': None} + _target_transforms: ClassVar[Mapping[str, str | None]] = {'object': None} - _rerender_params = ['object', 'dedent', 'extensions', 'css_classes'] + _rerender_params: ClassVar[List[str]] = [ + 'object', 'dedent', 'extensions', 'css_classes' + ] @classmethod - def applies(cls, obj): + def applies(cls, obj: Any) -> float | bool | None: if hasattr(obj, '_repr_markdown_'): return 0.3 elif isinstance(obj, str): @@ -374,16 +391,22 @@ class JSON(DivPaneBase): theme = param.ObjectSelector(default="dark", objects=["light", "dark"], doc=""" Whether the JSON tree view is expanded by default.""") - priority = None + priority: ClassVar[float | bool | None] = None + + _applies_kw: ClassVar[bool] = True - _applies_kw = True - _bokeh_model = _BkJSON - _rename: ClassVar[Mapping[str, str | None]] = {"name": None, "object": "text", "encoder": None} + _bokeh_model: ClassVar[Model] = _BkJSON - _rerender_params = ['object', 'depth', 'encoder', 'hover_preview', 'theme'] + _rename: ClassVar[Mapping[str, str | None]] = { + "name": None, "object": "text", "encoder": None + } + + _rerender_params: ClassVar[List[str]] = [ + 'object', 'depth', 'encoder', 'hover_preview', 'theme' + ] @classmethod - def applies(cls, obj, **params): + def applies(cls, obj: Any, **params) -> float | bool | None: if isinstance(obj, (list, dict)): try: json.dumps(obj, cls=params.get('encoder', cls.encoder)) diff --git a/panel/pane/media.py b/panel/pane/media.py index 17d36767e6..94b0158fb9 100644 --- a/panel/pane/media.py +++ b/panel/pane/media.py @@ -7,7 +7,9 @@ from base64 import b64encode from io import BytesIO -from typing import ClassVar, Mapping +from typing import ( + TYPE_CHECKING, Any, ClassVar, List, Mapping, Optional, +) import numpy as np import param @@ -16,6 +18,11 @@ from ..util import isfile, isurl from .base import PaneBase +if TYPE_CHECKING: + from bokeh.document import Document + from bokeh.model import Model + from pyviz_comms import Comm + class _MediaBase(PaneBase): @@ -44,22 +51,23 @@ class _MediaBase(PaneBase): muted = param.Boolean(default=False, doc=""" When True, it specifies that the output should be muted.""") - _default_mime = None + _default_mime: ClassVar[str] - _formats = [] + _formats: ClassVar[List[str]] - _media_type = None + _media_type: ClassVar[str] - _rename: ClassVar[Mapping[str, str | None]] = {'name': None, 'sample_rate': None, 'object': 'value'} + _rename: ClassVar[Mapping[str, str | None]] = { + 'name': None, 'sample_rate': None, 'object': 'value'} - _rerender_params = [] + _rerender_params: ClassVar[List[str]] = [] - _updates = True + _updates: ClassVar[bool] = True __abstract = True @classmethod - def applies(cls, obj): + def applies(cls, obj: Any) -> float | bool | None: if isinstance(obj, str): if isfile(obj) and any(obj.endswith('.'+fmt) for fmt in cls._formats): return True @@ -69,7 +77,10 @@ def applies(cls, obj): return True return False - def _get_model(self, doc, root=None, parent=None, comm=None): + def _get_model( + self, doc: Document, root: Optional[Model] = None, + parent: Optional[Model] = None, comm: Optional[Comm] = None + ) -> Model: props = self._process_param_change(self._init_params()) model = self._bokeh_model(**props) if root is None: @@ -148,7 +159,7 @@ class Audio(_MediaBase): _media_type = 'audio' @classmethod - def applies(cls, obj): + def applies(cls, obj: Any) -> float | bool | None: return (super().applies(obj) or (isinstance(obj, np.ndarray) and obj.ndim==1 and obj.dtype in [np.int16, np.uint16])) diff --git a/panel/pane/perspective.py b/panel/pane/perspective.py index a8cc140991..11f5742374 100644 --- a/panel/pane/perspective.py +++ b/panel/pane/perspective.py @@ -5,7 +5,9 @@ import warnings from enum import Enum -from typing import ClassVar, Mapping +from typing import ( + TYPE_CHECKING, ClassVar, List, Mapping, Optional, +) import numpy as np import param @@ -18,6 +20,11 @@ from ..viewable import Viewable from .base import PaneBase +if TYPE_CHECKING: + from bokeh.document import Document + from bokeh.model import Model + from pyviz_comms import Comm + DEFAULT_THEME = "material" THEMES_MAP = { "material": "perspective-viewer-material", @@ -317,11 +324,11 @@ class Perspective(PaneBase, ReactiveData): theme = param.ObjectSelector(default=DEFAULT_THEME, objects=THEMES, doc=""" The style of the PerspectiveViewer. For example material-dark""") - priority = None + priority: ClassVar[float | bool | None] = None - _data_params = ['object'] + _data_params: ClassVar[List[str]] = ['object'] - _rerender_params = ['object'] + _rerender_params: ClassVar[List[str]] = ['object'] _rename: ClassVar[Mapping[str, str | None]] = { 'computed_columns': None, @@ -330,7 +337,7 @@ class Perspective(PaneBase, ReactiveData): 'selection': None, } - _updates = True + _updates: ClassVar[bool] = True _deprecations = { 'computed_columns': 'expressions', @@ -467,7 +474,10 @@ def _process_property_change(self, msg): msg['aggregates'] = {self._as_digit(col): agg for col, agg in msg['aggregates'].items()} return msg - def _get_model(self, doc, root=None, parent=None, comm=None): + def _get_model( + self, doc: Document, root: Optional[Model] = None, + parent: Optional[Model] = None, comm: Optional[Comm] = None + ) -> Model: Perspective = lazy_load('panel.models.perspective', 'Perspective', isinstance(comm, JupyterComm), root) properties = self._process_param_change(self._init_params()) if properties.get('toggle_config'): @@ -482,5 +492,5 @@ def _get_model(self, doc, root=None, parent=None, comm=None): self._models[root.ref['id']] = (model, parent) return model - def _update(self, ref, model): + def _update(self, ref: str, model: Model) -> None: pass diff --git a/panel/pane/plot.py b/panel/pane/plot.py index bd553c77d4..9d062ef184 100644 --- a/panel/pane/plot.py +++ b/panel/pane/plot.py @@ -7,7 +7,9 @@ from contextlib import contextmanager from io import BytesIO -from typing import ClassVar, Mapping +from typing import ( + TYPE_CHECKING, Any, ClassVar, Mapping, Optional, +) import param @@ -25,6 +27,10 @@ from .ipywidget import IPyWidget from .markup import HTML +if TYPE_CHECKING: + from bokeh.document import Document + from pyviz_comms import Comm + FOLIUM_BEFORE = '
' FOLIUM_AFTER = '
' @@ -68,12 +74,12 @@ class Bokeh(PaneBase): theme = param.ClassSelector(default=None, class_=(Theme, str), doc=""" Bokeh theme to apply to the plot.""") - priority = 0.8 + priority: ClassVar[float | bool | None] = 0.8 _rename: ClassVar[Mapping[str, str | None]] = {'autodispatch': None, 'theme': None} @classmethod - def applies(cls, obj): + def applies(cls, obj: Any) -> float | bool | None: return isinstance(obj, LayoutDOM) @classmethod @@ -106,7 +112,10 @@ def _wrap_bokeh_callbacks(cls, root, bokeh_model, doc, comm): for cb in cbs ] - def _get_model(self, doc, root=None, parent=None, comm=None): + def _get_model( + self, doc: Document, root: Optional[Model] = None, + parent: Optional[Model] = None, comm: Optional[Comm] = None + ) -> Model: if root is None: return self.get_root(doc, comm) @@ -178,7 +187,7 @@ class Matplotlib(PNG, IPyWidget): _rerender_params = PNG._rerender_params + ['object', 'dpi', 'tight'] @classmethod - def applies(cls, obj): + def applies(cls, obj: Any) -> float | bool | None: if 'matplotlib' not in sys.modules: return False from matplotlib.figure import Figure @@ -221,7 +230,10 @@ def _update_dimensions(self): self.width = self.width or int(dpi * w) self.height = self.height or int(dpi * h) - def _get_model(self, doc, root=None, parent=None, comm=None): + def _get_model( + self, doc: Document, root: Optional[Model] = None, + parent: Optional[Model] = None, comm: Optional[Comm] = None + ) -> Model: self._update_dimensions() if not self.interactive: model = PNG._get_model(self, doc, root, parent, comm) @@ -243,7 +255,7 @@ def _get_model(self, doc, root=None, parent=None, comm=None): self._managers[root.ref['id']] = manager return model - def _update(self, ref=None, model=None): + def _update(self, ref: str, model: Model) -> None: if not self.interactive: self._update_dimensions() model.update(**self._get_properties()) @@ -287,7 +299,7 @@ class RGGPlot(PNG): _rerender_params = PNG._rerender_params + ['object', 'dpi', 'width', 'height'] @classmethod - def applies(cls, obj): + def applies(cls, obj: Any) -> float | bool | None: return type(obj).__name__ == 'GGPlot' and hasattr(obj, 'r_repr') def _img(self): @@ -308,10 +320,10 @@ class YT(HTML): provide additional space. """ - priority = 0.5 + priority: ClassVar[float | bool | None] = 0.5 @classmethod - def applies(cls, obj): + def applies(cls, obj: bool) -> float | bool | None: return (getattr(obj, '__module__', '').startswith('yt.') and hasattr(obj, "plots") and hasattr(obj, "_repr_html_")) @@ -345,10 +357,10 @@ class Folium(HTML): 'fixed', 'stretch_width', 'stretch_height', 'stretch_both', 'scale_width', 'scale_height', 'scale_both', None]) - priority = 0.6 + priority: ClassVar[float | bool | None] = 0.6 @classmethod - def applies(cls, obj): + def applies(cls, obj: Any) -> float | bool | None: return (getattr(obj, '__module__', '').startswith('folium.') and hasattr(obj, "_repr_html_")) diff --git a/panel/pane/plotly.py b/panel/pane/plotly.py index c922b6f991..db1aaf1933 100644 --- a/panel/pane/plotly.py +++ b/panel/pane/plotly.py @@ -2,6 +2,12 @@ Defines a PlotlyPane which renders a plotly plot using PlotlyPlot bokeh model. """ +from __future__ import annotations + +from typing import ( + TYPE_CHECKING, Any, ClassVar, Optional, +) + import numpy as np import param @@ -12,6 +18,11 @@ from ..viewable import Layoutable from .base import PaneBase +if TYPE_CHECKING: + from bokeh.document import Document + from bokeh.model import Model + from pyviz_comms import Comm + class Plotly(PaneBase): """ @@ -66,12 +77,12 @@ class Plotly(PaneBase): _render_count = param.Integer(default=0, doc=""" Number of renders, increment to trigger re-render""") - priority = 0.8 + priority: ClassVar[float | bool | None] = 0.8 - _updates = True + _updates: ClassVar[bool] = True @classmethod - def applies(cls, obj): + def applies(cls, obj: Any) -> float | bool | None: return ((isinstance(obj, list) and obj and all(cls.applies(o) for o in obj)) or hasattr(obj, 'to_plotly_json') or (isinstance(obj, dict) and 'data' in obj and 'layout' in obj)) @@ -256,7 +267,10 @@ def _init_params(self): params['sizing_mode'] = 'stretch_both' return params - def _get_model(self, doc, root=None, parent=None, comm=None): + def _get_model( + self, doc: Document, root: Optional[Model] = None, + parent: Optional[Model] = None, comm: Optional[Comm] = None + ) -> Model: PlotlyPlot = lazy_load('panel.models.plotly', 'PlotlyPlot', isinstance(comm, JupyterComm), root) model = PlotlyPlot(**self._init_params()) if root is None: @@ -265,7 +279,7 @@ def _get_model(self, doc, root=None, parent=None, comm=None): self._models[root.ref['id']] = (model, parent) return model - def _update(self, ref=None, model=None): + def _update(self, ref: str, model: Model) -> None: if self.object is None: model.update(data=[], layout={}) model._render_count += 1 diff --git a/panel/pane/streamz.py b/panel/pane/streamz.py index 2a2dcec378..c4cbc63641 100644 --- a/panel/pane/streamz.py +++ b/panel/pane/streamz.py @@ -5,12 +5,19 @@ import sys -from typing import ClassVar, Mapping +from typing import ( + TYPE_CHECKING, Any, ClassVar, Mapping, Optional, +) import param from .base import ReplacementPane +if TYPE_CHECKING: + from bokeh.document import Document + from bokeh.model import Model + from pyviz_comms import Comm + class Streamz(ReplacementPane): """ @@ -50,12 +57,15 @@ def _setup_stream(self): self._stream = self.object.latest().rate_limit(self.rate_limit).gather() self._stream.sink(self._update_inner) - def _get_model(self, doc, root=None, parent=None, comm=None): + def _get_model( + self, doc: Document, root: Optional[Model] = None, + parent: Optional[Model] = None, comm: Optional[Comm] = None + ) -> Model: model = super()._get_model(doc, root, parent, comm) self._setup_stream() return model - def _cleanup(self, root=None): + def _cleanup(self, root: Model | None = None): super()._cleanup(root) if not self._pane._models and self._stream: self._stream.destroy() @@ -66,7 +76,7 @@ def _cleanup(self, root=None): #---------------------------------------------------------------- @classmethod - def applies(cls, obj): + def applies(cls, obj: Any) -> float | bool | None: if 'streamz' in sys.modules: from streamz import Stream return isinstance(obj, Stream) diff --git a/panel/pane/vega.py b/panel/pane/vega.py index 5f09c5961f..54555365e5 100644 --- a/panel/pane/vega.py +++ b/panel/pane/vega.py @@ -3,7 +3,9 @@ import sys from functools import partial -from typing import ClassVar, Mapping +from typing import ( + TYPE_CHECKING, Any, ClassVar, Mapping, Optional, +) import numpy as np import param @@ -15,6 +17,11 @@ from ..viewable import Layoutable from .base import PaneBase +if TYPE_CHECKING: + from bokeh.document import Document + from bokeh.model import Model + from pyviz_comms import Comm + def ds_as_cds(dataset): """ @@ -106,11 +113,11 @@ class Vega(PaneBase): 'excel', 'ggplot2', 'quartz', 'vox', 'fivethirtyeight', 'dark', 'latimes', 'urbaninstitute', 'googlecharts']) - priority = 0.8 + priority: ClassVar[float | bool | None] = 0.8 _rename: ClassVar[Mapping[str, str | None]] = {'selection': None, 'debounce': None} - _updates = True + _updates: ClassVar[bool] = True def __init__(self, object=None, **params): super().__init__(object, **params) @@ -148,7 +155,7 @@ def is_altair(cls, obj): return False @classmethod - def applies(cls, obj): + def applies(cls, obj: Any) -> float | bool | None: if isinstance(obj, dict) and 'vega' in obj.get('$schema', '').lower(): return True return cls.is_altair(obj) @@ -239,7 +246,10 @@ def _process_event(self, event): value = list(value) self.selection.param.update(**{name: value}) - def _get_model(self, doc, root=None, parent=None, comm=None): + def _get_model( + self, doc: Document, root: Optional[Model] = None, + parent: Optional[Model] = None, comm: Optional[Comm] = None + ) -> Model: VegaPlot = lazy_load('panel.models.vega', 'VegaPlot', isinstance(comm, JupyterComm), root) sources = {} if self.object is None: @@ -262,7 +272,7 @@ def _get_model(self, doc, root=None, parent=None, comm=None): self._models[root.ref['id']] = (model, parent) return model - def _update(self, ref=None, model=None): + def _update(self, ref: str, model: Model) -> None: if self.object is None: json = None else: diff --git a/panel/pane/vtk/vtk.py b/panel/pane/vtk/vtk.py index d486748f57..61ef1fd99a 100644 --- a/panel/pane/vtk/vtk.py +++ b/panel/pane/vtk/vtk.py @@ -9,7 +9,9 @@ import zipfile from abc import abstractmethod -from typing import ClassVar, Mapping +from typing import ( + TYPE_CHECKING, Any, ClassVar, Dict, Mapping, Optional, +) from urllib.request import urlopen import numpy as np @@ -23,6 +25,11 @@ from ..base import Pane, PaneBase from .enums import PRESET_CMAPS +if TYPE_CHECKING: + from bokeh.document import Document + from bokeh.model import Model + from pyviz_comms import Comm + base64encode = lambda x: base64.b64encode(x).decode('utf-8') @@ -72,7 +79,10 @@ def _process_param_change(self, msg): msg['axes'] = VTKAxes(**axes) return msg - def _update_model(self, events, msg, root, model, doc, comm): + def _update_model( + self, events: Dict[str, param.parameterized.Event], msg: Dict[str, Any], + root: Model, model: Model, doc: Document, comm: Optional[Comm] + ) -> None: if 'axes' in msg and msg['axes'] is not None: VTKAxes = getattr(sys.modules['panel.models.vtk'], 'VTKAxes') axes = msg['axes'] @@ -367,9 +377,12 @@ def __init__(self, object=None, **params): super(VTKRenderWindow, self).__init__(object, **params) if object is not None: self.color_mappers = self.get_color_mappers() - self._update() + self._update(None, None) - def _get_model(self, doc, root=None, parent=None, comm=None): + def _get_model( + self, doc: Document, root: Optional[Model] = None, + parent: Optional[Model] = None, comm: Optional[Comm] = None + ) -> Model: VTKSynchronizedPlot = lazy_load( 'panel.models.vtk', 'VTKSynchronizedPlot', isinstance(comm, JupyterComm), root ) @@ -386,7 +399,7 @@ def _get_model(self, doc, root=None, parent=None, comm=None): self._models[root.ref['id']] = (model, parent) return model - def _update(self, ref=None, model=None): + def _update(self, ref: str, model: Model) -> None: import panel.pane.vtk.synchronizable_serializer as rws context = rws.SynchronizationContext( id_root=make_globally_unique_id(), @@ -429,7 +442,10 @@ def __init__(self, object=None, **params): super().__init__(object, **params) self._contexts = {} - def _get_model(self, doc, root=None, parent=None, comm=None): + def _get_model( + self, doc: Document, root: Optional[Model] = None, + parent: Optional[Model] = None, comm: Optional[Comm] = None + ) -> Model: VTKSynchronizedPlot = lazy_load( 'panel.models.vtk', 'VTKSynchronizedPlot', isinstance(comm, JupyterComm), root ) @@ -455,12 +471,12 @@ def _get_model(self, doc, root=None, parent=None, comm=None): self._models[root.ref['id']] = (model, parent) return model - def _cleanup(self, root): + def _cleanup(self, root: Model | None = None) -> None: ref = root.ref['id'] self._contexts.pop(ref, None) super()._cleanup(root) - def _update(self, ref=None, model=None): + def _update(self, ref: str, model: Model) -> None: context = self._contexts[model.id] scene, arrays, annotations = self._serialize_ren_win( self.object, @@ -634,10 +650,10 @@ class VTKVolume(AbstractVTK): def __init__(self, object=None, **params): super().__init__(object, **params) self._sub_spacing = self.spacing - self._update() + self._update(None, None) @classmethod - def applies(cls, obj): + def applies(cls, obj: Any) -> float | bool | None: if ((isinstance(obj, np.ndarray) and obj.ndim == 3) or any([isinstance(obj, k) for k in cls._serializers.keys()])): return True @@ -647,7 +663,10 @@ def applies(cls, obj): import vtk return isinstance(obj, vtk.vtkImageData) - def _get_model(self, doc, root=None, parent=None, comm=None): + def _get_model( + self, doc: Document, root: Optional[Model] = None, + parent: Optional[Model] = None, comm: Optional[Comm] = None + ) -> Model: VTKVolumePlot = lazy_load( 'panel.models.vtk', 'VTKVolumePlot', isinstance(comm, JupyterComm), root ) @@ -696,7 +715,7 @@ def _process_property_change(self, msg): msg[k] = int(np.round(v * ori_dim[index] / sub_dim[index])) return msg - def _update(self, ref=None, model=None): + def _update(self, ref: str, model: Model) -> None: self._volume_data = self._get_volume_data() if self._volume_data is not None: self._orginal_dimensions = self._get_object_dimensions() @@ -809,11 +828,14 @@ def __init__(self, object=None, **params): self._vtkjs = None @classmethod - def applies(cls, obj): + def applies(cls, obj: Any) -> float | bool | None: if isinstance(obj, str) and obj.endswith('.vtkjs'): return True - def _get_model(self, doc, root=None, parent=None, comm=None): + def _get_model( + self, doc: Document, root: Optional[Model] = None, + parent: Optional[Model] = None, comm: Optional[Comm] = None + ) -> Model: """ Should return the bokeh model to be rendered. """ @@ -843,7 +865,7 @@ def _get_vtkjs(self): self._vtkjs = vtkjs return self._vtkjs - def _update(self, ref=None, model=None): + def _update(self, ref: str, model: Model) -> None: self._vtkjs = None vtkjs = self._get_vtkjs() model.data = base64encode(vtkjs) if vtkjs is not None else vtkjs diff --git a/panel/param.py b/panel/param.py index 3e3b494542..f83556a5a3 100644 --- a/panel/param.py +++ b/panel/param.py @@ -2,6 +2,8 @@ Defines the Param pane which converts Parameterized classes into a set of widgets. """ +from __future__ import annotations + import inspect import itertools import json @@ -12,6 +14,9 @@ from collections.abc import Callable from contextlib import contextmanager from functools import partial +from typing import ( + TYPE_CHECKING, Any, ClassVar, List, Mapping, Optional, +) import param @@ -37,8 +42,13 @@ ) from .widgets.button import _ButtonBase +if TYPE_CHECKING: + from bokeh.document import Document + from bokeh.model import Model + from pyviz_comms import Comm + -def SingleFileSelector(pobj): +def SingleFileSelector(pobj: param.Parameter) -> Widget: """ Determines whether to use a TextInput or Select widget for FileSelector """ @@ -48,7 +58,7 @@ def SingleFileSelector(pobj): return TextInput -def LiteralInputTyped(pobj): +def LiteralInputTyped(pobj: param.Parameter) -> Widget: if isinstance(pobj, param.Tuple): return type(str('TupleInput'), (LiteralInput,), {'type': tuple}) elif isinstance(pobj, param.Number): @@ -151,7 +161,7 @@ class Param(PaneBase): Dictionary of widget overrides, mapping from parameter name to widget class.""") - mapping = { + mapping: ClassVar[Mapping[param.Parameter, Widget | Callable[[param.Parameter], Widget]]] = { param.Action: Button, param.Array: ArrayInput, param.Boolean: Checkbox, @@ -183,11 +193,11 @@ class Param(PaneBase): if bokeh_version >= Version('2.4.3'): mapping[param.DateRange] = DatetimeRangeSlider - priority = 0.1 + priority: ClassVar[float | bool | None] = 0.1 - _unpack = True + _unpack: ClassVar[bool] = True - _rerender_params = [] + _rerender_params: ClassVar[List[str]] = [] def __init__(self, object=None, **params): if isinstance(object, param.Parameter): @@ -647,12 +657,15 @@ def _get_widgets(self): widgets += [(pname, self.widget(pname)) for pname in self._ordered_params] return OrderedDict(widgets) - def _get_model(self, doc, root=None, parent=None, comm=None): + def _get_model( + self, doc: Document, root: Optional[Model] = None, + parent: Optional[Model] = None, comm: Optional[Comm] = None + ) -> Model: model = self.layout._get_model(doc, root, parent, comm) self._models[root.ref['id']] = (model, parent) return model - def _cleanup(self, root): + def _cleanup(self, root: Model | None = None) -> None: self.layout._cleanup(root) super()._cleanup(root) @@ -661,7 +674,7 @@ def _cleanup(self, root): #---------------------------------------------------------------- @classmethod - def applies(cls, obj): + def applies(cls, obj: Any) -> float | bool | None: return (is_parameterized(obj) or isinstance(obj, param.parameterized.Parameters) or (isinstance(obj, param.Parameter) and obj.owner is not None)) @@ -694,7 +707,10 @@ def select(self, selector=None): """ return super().select(selector) + self.layout.select(selector) - def get_root(self, doc=None, comm=None, preprocess=True): + def get_root( + self, doc: Optional[Document] = None, comm: Optional[Comm] = None, + preprocess: bool = True + ) -> Model: """ Returns the root model and applies pre-processing hooks @@ -853,7 +869,10 @@ def update_pane(*events): watcher = pobj.param.watch(update_pane, ps, p.what) self._callbacks.append(watcher) - def _get_model(self, doc, root=None, parent=None, comm=None): + def _get_model( + self, doc: Document, root: Optional[Model] = None, + parent: Optional[Model] = None, comm: Optional[Comm] = None + ) -> Model: if not self._evaled: self._replace_pane(force=True) return super()._get_model(doc, root, parent, comm) @@ -863,7 +882,7 @@ def _get_model(self, doc, root=None, parent=None, comm=None): #---------------------------------------------------------------- @classmethod - def applies(cls, obj): + def applies(cls, obj: Any) -> float | bool | None: return inspect.ismethod(obj) and isinstance(get_method_owner(obj), param.Parameterized) @@ -877,7 +896,7 @@ class ParamFunction(ParamMethod): a widget to some other output. """ - priority = 0.6 + priority: ClassVar[float | bool | None] = 0.6 def _link_object_params(self): deps = getattr(self.object, '_dinfo', {}) @@ -913,7 +932,7 @@ def _link_object_params(self): #---------------------------------------------------------------- @classmethod - def applies(cls, obj): + def applies(cls, obj: Any) -> float | bool | None: if isinstance(obj, types.FunctionType): if hasattr(obj, '_dinfo'): return True diff --git a/panel/reactive.py b/panel/reactive.py index a66ae92b89..f9ac7490df 100644 --- a/panel/reactive.py +++ b/panel/reactive.py @@ -1405,7 +1405,7 @@ def _loaded(cls) -> bool: cls._extension_name in ReactiveHTMLMetaclass._loaded_extensions ) - def _cleanup(self, root: Model | None) -> None: + def _cleanup(self, root: Model | None = None) -> None: for child, panes in self._panes.items(): for pane in panes: pane._cleanup(root) diff --git a/panel/viewable.py b/panel/viewable.py index 2548a9b512..4eb8931abb 100644 --- a/panel/viewable.py +++ b/panel/viewable.py @@ -476,7 +476,7 @@ def _get_model( """ raise NotImplementedError - def _cleanup(self, root: 'Model' | None) -> None: + def _cleanup(self, root: Model | None = None) -> None: """ Clean up method which is called when a Viewable is destroyed. @@ -537,7 +537,7 @@ def _server_destroy(self, session_context: 'BokehSessionContext') -> None: def get_root( self, doc: Optional[Document] = None, comm: Optional[Comm] = None, preprocess: bool = True - ) -> 'Model': + ) -> Model: """ Returns the root model and applies pre-processing hooks diff --git a/panel/widgets/ace.py b/panel/widgets/ace.py index 968b94fda8..cac813d7af 100644 --- a/panel/widgets/ace.py +++ b/panel/widgets/ace.py @@ -3,7 +3,9 @@ """ from __future__ import annotations -from typing import ClassVar, Mapping +from typing import ( + TYPE_CHECKING, ClassVar, Mapping, Optional, +) import param @@ -13,6 +15,11 @@ from ..util import lazy_load from .base import Widget +if TYPE_CHECKING: + from bokeh.document import Document + from bokeh.model import Model + from pyviz_comms import Comm + class Ace(Widget): """ @@ -55,14 +62,17 @@ def __init__(self, **params): self.param.watch(self._update_disabled, ['disabled', 'readonly']) self.jslink(self, readonly='disabled', bidirectional=True) - def _get_model(self, doc, root=None, parent=None, comm=None): + def _get_model( + self, doc: Document, root: Optional[Model] = None, + parent: Optional[Model] = None, comm: Optional[Comm] = None + ) -> Model: if self._widget_type is None: self._widget_type = lazy_load( 'panel.models.ace', 'AcePlot', isinstance(comm, JupyterComm), root ) return super()._get_model(doc, root, parent, comm) - def _update_disabled(self, *events): + def _update_disabled(self, *events: param.parameterized.Event): for event in events: if event.name == 'disabled': self.readonly = event.new diff --git a/panel/widgets/base.py b/panel/widgets/base.py index 3494182ccc..8ccdaf602a 100644 --- a/panel/widgets/base.py +++ b/panel/widgets/base.py @@ -9,11 +9,12 @@ from typing import ( TYPE_CHECKING, Any, Callable, ClassVar, List, Mapping, Optional, Tuple, + Type, ) import param # type: ignore -from ..layout import Row +from ..layout.base import Row from ..reactive import Reactive from ..viewable import Layoutable, Viewable @@ -22,7 +23,7 @@ from bokeh.model import Model from pyviz_comms import Comm - from ..layout import Panel + from ..layout.base import ListPanel class Widget(Reactive): @@ -48,10 +49,10 @@ class Widget(Reactive): _rename: ClassVar[Mapping[str, str | None]] = {'name': 'title'} # Whether the widget supports embedding - _supports_embed: bool = False + _supports_embed: ClassVar[bool] = False # Declares the Bokeh model type of the widget - _widget_type: 'Model' = None + _widget_type: ClassVar[Type[Model] | None] = None __abstract = True @@ -91,9 +92,9 @@ def from_param(cls, parameter: param.Parameter, **params) -> Viewable: return layout[0] def _get_model( - self, doc: 'Document', root: Optional['Model'] = None, - parent: Optional['Model'] = None, comm: Optional['Comm'] = None - ) -> 'Model': + self, doc: Document, root: Optional[Model] = None, + parent: Optional[Model] = None, comm: Optional[Comm] = None + ) -> Model: model = self._widget_type(**self._process_param_change(self._init_params())) if root is None: root = model @@ -147,7 +148,7 @@ class CompositeWidget(Widget): widgets """ - _composite_type: 'Panel' = Row + _composite_type: ClassVar[Type[ListPanel]] = Row __abstract = True @@ -188,14 +189,14 @@ def select( objects += obj.select(selector) return objects - def _cleanup(self, root: 'Model') -> None: + def _cleanup(self, root: Model | None = None) -> None: self._composite._cleanup(root) super()._cleanup(root) def _get_model( - self, doc: 'Document', root: Optional['Model'] = None, - parent: Optional['Model'] = None, comm: Optional['Comm'] = None - ) -> 'Model': + self, doc: Document, root: Optional[Model] = None, + parent: Optional[Model] = None, comm: Optional[Comm] = None + ) -> Model: model = self._composite._get_model(doc, root, parent, comm) if root is None: root = parent = model diff --git a/panel/widgets/button.py b/panel/widgets/button.py index dac4658907..baa86951fa 100644 --- a/panel/widgets/button.py +++ b/panel/widgets/button.py @@ -7,6 +7,7 @@ from functools import partial from typing import ( TYPE_CHECKING, Any, Callable, ClassVar, Dict, List, Mapping, Optional, + Type, ) import param @@ -20,9 +21,11 @@ from .base import Widget if TYPE_CHECKING: - from panel.reactive import JSLinkTarget + from bokeh.document import Document + from bokeh.model import Model + from pyviz_comms import Comm - from ..links import Link + from ..links import JSLinkTarget, Link BUTTON_TYPES: List[str] = ['default', 'primary', 'success', 'warning', 'danger','light'] @@ -44,9 +47,12 @@ class _ClickButton(_ButtonBase): __abstract = True - _event = 'button_click' + _event: ClassVar[str] = 'button_click' - def _get_model(self, doc, root=None, parent=None, comm=None): + def _get_model( + self, doc: Document, root: Optional[Model] = None, + parent: Optional[Model] = None, comm: Optional[Comm] = None + ) -> Model: model = super()._get_model(doc, root, parent, comm) if comm: model.on_event(self._event, self._comm_event) @@ -97,7 +103,9 @@ def jscallback(self, args: Dict[str, Any] = {}, **callbacks: str) -> Callback: for k, v in list(callbacks.items()): if k == 'clicks': k = 'event:'+self._event - callbacks[k] = self._rename.get(v, v) + val = self._rename.get(v, v) + if val is not None: + callbacks[k] = val return Callback(self, code=callbacks, args=args) @@ -127,19 +135,21 @@ class Button(_ClickButton): _rename: ClassVar[Mapping[str, str | None]] = {'clicks': None, 'name': 'label', 'value': None} - _target_transforms = {'event:button_click': None, 'value': None} + _target_transforms: ClassVar[Mapping[str, str | None]] = { + 'event:button_click': None, 'value': None + } - _widget_type = _BkButton + _widget_type: ClassVar[Type[Model]] = _BkButton @property - def _linkable_params(self): + def _linkable_params(self) -> List[str]: return super()._linkable_params + ['value'] def jslink( - self, target: 'JSLinkTarget', code: Optional[Dict[str, str]] = None, + self, target: JSLinkTarget, code: Optional[Dict[str, str]] = None, args: Optional[Dict[str, Any]] = None, bidirectional: bool = False, **links: str - ) -> 'Link': + ) -> Link: """ Links properties on the this Button to those on the `target` object in Javascript (JS) code. @@ -215,9 +225,9 @@ class Toggle(_ButtonBase): _rename: ClassVar[Mapping[str, str | None]] = {'value': 'active', 'name': 'label'} - _supports_embed = True + _supports_embed: ClassVar[bool] = True - _widget_type = _BkToggle + _widget_type: ClassVar[Type[Model]] = _BkToggle def _get_embed_state(self, root, values=None, max_opts=3): return (self, self._models[root.ref['id']][0], [False, True], @@ -252,11 +262,11 @@ class MenuButton(_ClickButton): split = param.Boolean(default=False, doc=""" Whether to add separate dropdown area to button.""") - _widget_type = _BkDropdown + _event: ClassVar[str] = 'menu_item_click' _rename: ClassVar[Mapping[str, str | None]] = {'name': 'label', 'items': 'menu', 'clicked': None} - _event = 'menu_item_click' + _widget_type: ClassVar[Type[Model]] = _BkDropdown def _process_event(self, event): if isinstance(event, MenuItemClick): diff --git a/panel/widgets/debugger.py b/panel/widgets/debugger.py index 69c3e80ac3..ba7fed8d19 100644 --- a/panel/widgets/debugger.py +++ b/panel/widgets/debugger.py @@ -6,7 +6,9 @@ import logging -from typing import ClassVar, Mapping +from typing import ( + ClassVar, Dict, List, Mapping, +) import param @@ -39,7 +41,6 @@ def __init__(self, *args, only_last=True, **kwargs): self.only_last = only_last def format(self, record): - record.message = record.getMessage() if self.usesTime(): record.asctime = self.formatTime(record, self.datefmt) @@ -118,7 +119,7 @@ class DebuggerButtons(ReactiveHTML): clears = param.Integer(default=0) - _template = """ + _template: ClassVar[str] = """