From c89d8e4f805402f53cd8694dd8e9d192d02d710d Mon Sep 17 00:00:00 2001 From: Philipp Rudiger Date: Fri, 10 Jun 2022 12:30:11 +0200 Subject: [PATCH] Add types for all Widget and Pane class variables --- panel/interact.py | 8 ++- panel/io/location.py | 5 +- panel/io/notifications.py | 3 +- panel/layout/accordion.py | 7 +- panel/layout/base.py | 12 ++-- panel/layout/card.py | 17 +++-- panel/layout/grid.py | 16 ++++- panel/layout/tabs.py | 7 +- 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 | 18 ++++-- panel/pane/perspective.py | 22 +++++-- 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 | 48 ++++++++++---- panel/param.py | 47 ++++++++++---- panel/viewable.py | 4 +- panel/widgets/ace.py | 16 ++++- panel/widgets/base.py | 20 +++--- panel/widgets/button.py | 34 ++++++---- panel/widgets/debugger.py | 13 ++-- panel/widgets/file_selector.py | 28 ++++---- 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 | 109 +++++++++++++++++++++----------- panel/widgets/text_to_speech.py | 11 +++- panel/widgets/texteditor.py | 14 +++- 42 files changed, 792 insertions(+), 382 deletions(-) diff --git a/panel/interact.py b/panel/interact.py index 4981c160105..092523e435c 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 0bfa3b78178..b084b0e92fd 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 a5517c2f0ec..d2283b445ba 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 a2ccc9669c3..85f5a17cde6 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 959d719713f..b4960944e1d 100644 --- a/panel/layout/base.py +++ b/panel/layout/base.py @@ -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) @@ -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) @@ -764,9 +764,13 @@ 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 diff --git a/panel/layout/card.py b/panel/layout/card.py index 3d995059fa5..7556ec4b8b4 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 2946aa3d1e2..aae90214eb8 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 f47b2034758..83ef1a84b50 100644 --- a/panel/layout/tabs.py +++ b/panel/layout/tabs.py @@ -4,7 +4,7 @@ from __future__ import annotations from collections import defaultdict -from typing import ClassVar, Mapping +from typing import TYPE_CHECKING, ClassVar, Mapping import param @@ -14,6 +14,9 @@ from ..viewable import Layoutable from .base import NamedListPanel +if TYPE_CHECKING: + from bokeh.model import Model + class Tabs(NamedListPanel): """ @@ -74,7 +77,7 @@ 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']] diff --git a/panel/pane/alert.py b/panel/pane/alert.py index 781c9f48587..28f4a8a4d04 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 598ec1d61e6..3ef815d190c 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: Optional[str] = None, model: Optional[Model] = None) -> 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 93104015d8e..947b8be1238 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: Optional[str] = None, model: Optional[Model] = None) -> 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 8a0d0c5324e..ca4bec50770 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 7e2cb378980..527c4df1258 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 7439beb6749..253f0f25168 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, +) 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, 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 d2551ad7fb9..86c06ff0095 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 259bfb99bda..a88649f9dbc 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 661626e7ff4..a2492d1871b 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: Optional[str] = None, model: Optional[Model] = None) -> 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 17d36767e62..9a9f7a5c060 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, 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): @@ -59,7 +66,7 @@ class _MediaBase(PaneBase): __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 +76,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 +158,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 a8cc140991d..42666a360f0 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'): diff --git a/panel/pane/plot.py b/panel/pane/plot.py index bd553c77d48..f4894766e5b 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: Optional[str] = None, model: Optional[Model] = None) -> 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 c922b6f991b..41b82374d4f 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: Optional[str] = None, model: Optional[Model] = None) -> 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 2a2dcec378b..c4cbc636416 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 5f09c5961f9..35f44fe0b03 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: Optional[str] = None, model: Optional[Model] = None) -> None: if self.object is None: json = None else: diff --git a/panel/pane/vtk/vtk.py b/panel/pane/vtk/vtk.py index d486748f572..19ece2d6425 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'] @@ -369,7 +379,10 @@ def __init__(self, object=None, **params): self.color_mappers = self.get_color_mappers() self._update() - 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: Optional[str] = None, model: Optional[Model] = None) -> 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: Optional[str] = None, model: Optional[Model] = None) -> None: context = self._contexts[model.id] scene, arrays, annotations = self._serialize_ren_win( self.object, @@ -637,7 +653,7 @@ def __init__(self, object=None, **params): self._update() @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: Optional[str] = None, model: Optional[Model] = None) -> 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: Optional[str] = None, model: Optional[Model] = None) -> 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 3e3b4945427..f83556a5a3c 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/viewable.py b/panel/viewable.py index 2548a9b5125..4eb8931abb5 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 968b94fda8a..cac813d7afd 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 3494182ccc9..eb5552219ff 100644 --- a/panel/widgets/base.py +++ b/panel/widgets/base.py @@ -48,10 +48,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[Model] = None __abstract = True @@ -91,9 +91,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 +147,7 @@ class CompositeWidget(Widget): widgets """ - _composite_type: 'Panel' = Row + _composite_type: ClassVar[Panel] = Row __abstract = True @@ -188,14 +188,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 dac46589078..2ef32e49e75 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) @@ -127,19 +133,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 +223,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 +260,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 69c3e80ac31..796d4e7b740 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] = """