diff --git a/panel/command/__init__.py b/panel/command/__init__.py index c7928b2cc5..0eade97b98 100644 --- a/panel/command/__init__.py +++ b/panel/command/__init__.py @@ -11,12 +11,25 @@ from bokeh.util.strings import nice_join from .. import __version__ +from ..config import config from .bundle import Bundle from .compile import Compile from .convert import Convert from .oauth_secret import OAuthSecret from .serve import Serve +description = """\ +Found a Bug or Have a Feature Request? +Open an issue at: https://github.com/holoviz/panel/issues + +Have a Question? +Ask on our Discord chat server: https://discord.gg/rb6gPXbdAr + +Need Help? +Ask a question on our forum: https://discourse.holoviz.org + +For more information, see the documentation at: https://panel.holoviz.org """ + def transform_cmds(argv): """ @@ -51,77 +64,36 @@ def transform_cmds(argv): def main(args: list[str] | None = None): from bokeh.command.subcommands import all as bokeh_commands - bokeh_commands = bokeh_commands + [OAuthSecret, Compile, Convert, Bundle] - - description = """\ -Found a Bug or Have a Feature Request? -Open an issue at: https://github.com/holoviz/panel/issues - -Have a Question? -Ask on our Discord chat server: https://discord.gg/rb6gPXbdAr - -Need Help? -Ask a question on our forum: https://discourse.holoviz.org - -For more information, see the documentation at: https://panel.holoviz.org """ - parser = argparse.ArgumentParser( prog="panel", epilog="See ' --help' to read about a specific subcommand.", description=description, formatter_class=argparse.RawTextHelpFormatter ) - parser.add_argument('-v', '--version', action='version', version=__version__) - subs = parser.add_subparsers(help="Sub-commands") - for cls in bokeh_commands: - if cls is BkServe: - subparser = subs.add_parser(Serve.name, help=Serve.help) - serve_subcommand = Serve(parser=subparser) - subparser.set_defaults(invoke=serve_subcommand.invoke) - elif cls is Compile: - subparser = subs.add_parser(Compile.name, help=Compile.help) - compile_subcommand = Compile(parser=subparser) - subparser.set_defaults(invoke=compile_subcommand.invoke) - elif cls is Convert: - subparser = subs.add_parser(Convert.name, help=Convert.help) - convert_subcommand = Convert(parser=subparser) - subparser.set_defaults(invoke=convert_subcommand.invoke) - elif cls is Bundle: - subparser = subs.add_parser(Bundle.name, help=Bundle.help) - bundle_subcommand = Bundle(parser=subparser) - subparser.set_defaults(invoke=bundle_subcommand.invoke) - else: - subs.add_parser(cls.name, help=cls.help) + commands = list(bokeh_commands) + for command in commands: + if command is not BkServe: + subs.add_parser(command.name, help=command.help) + for extra in (Bundle, Compile, Convert, OAuthSecret, Serve): + commands.append(extra) + subparser = subs.add_parser(extra.name, help=extra.help) + subcommand = extra(parser=subparser) + subparser.set_defaults(invoke=subcommand.invoke) if len(sys.argv) == 1: - all_commands = sorted([c.name for c in bokeh_commands]) + all_commands = sorted([c.name for c in commands]) die(f"ERROR: Must specify subcommand, one of: {nice_join(all_commands)}") - - if sys.argv[1] in ('--help', '-h'): - parser = parser.parse_args(sys.argv[1:]) - parser.invoke(args) - sys.exit() - - if len(sys.argv) > 1 and any(sys.argv[1] == c.name for c in bokeh_commands): + elif len(sys.argv) > 1 and any(sys.argv[1] == c.name for c in commands): sys.argv = transform_cmds(sys.argv) - if sys.argv[1] == 'serve': - args = parser.parse_args(sys.argv[1:]) + if sys.argv[1] in ('bundle', 'compile', 'convert', 'serve', 'help'): + parsed_args = parser.parse_args(sys.argv[1:]) try: - ret = args.invoke(args) + ret = parsed_args.invoke(parsed_args) except Exception as e: + if config.dev: + raise e die("ERROR: " + str(e)) - elif sys.argv[1] == 'oauth-secret': - ret = OAuthSecret(parser).invoke(args) - elif sys.argv[1] == 'convert': - args = parser.parse_args(sys.argv[1:]) - ret = Convert(parser).invoke(args) - elif sys.argv[1] == 'bundle': - args = parser.parse_args(sys.argv[1:]) - ret = Bundle(parser).invoke(args) - elif sys.argv[1] == 'compile': - args = parser.parse_args(sys.argv[1:]) - ret = Compile(parser).invoke(args) else: ret = bokeh_entry_point() else: diff --git a/panel/custom.py b/panel/custom.py index 130c0eb5c6..c8a1f6350e 100644 --- a/panel/custom.py +++ b/panel/custom.py @@ -670,7 +670,7 @@ class CounterButton(pn.custom.ReactComponent): @classproperty # type: ignore def _exports__(cls) -> ExportSpec: imports = cls._importmap.get('imports', {}) - exports = { + exports: dict[str, list[str | tuple[str, ...]]] = { "react": ["*React"], "react-dom/client": [("createRoot",)] } diff --git a/panel/pane/deckgl.py b/panel/pane/deckgl.py index 98e4d38dab..1f2930d8e1 100644 --- a/panel/pane/deckgl.py +++ b/panel/pane/deckgl.py @@ -156,7 +156,7 @@ def _process_data(cls, data): return {col: np.asarray(vals) for col, vals in columns.items()} @classmethod - def _update_sources(cls, json_data, sources): + def _update_sources(cls, json_data, sources: list[ColumnDataSource]): layers = json_data.get('layers', []) # Create index of sources by columns @@ -284,7 +284,8 @@ def _get_model( ) properties = self._get_properties(doc) data = properties.pop('data') - properties['data_sources'] = sources = [] + sources: list[ColumnDataSource] = [] + properties['data_sources'] = sources self._update_sources(data, sources) properties['layers'] = data.pop('layers', []) properties['initialViewState'] = data.pop('initialViewState', {}) diff --git a/panel/pane/echarts.py b/panel/pane/echarts.py index ca211b72c5..be7a5ace9e 100644 --- a/panel/pane/echarts.py +++ b/panel/pane/echarts.py @@ -115,7 +115,7 @@ def _process_param_change(self, params): props['sizing_mode'] = 'stretch_both' return props - def _get_properties(self, document: Document): + def _get_properties(self, document: Document | None) -> dict[str, Any]: props = super()._get_properties(document) props['event_config'] = { event: list(queries) for event, queries in self._py_callbacks.items() diff --git a/panel/pane/equation.py b/panel/pane/equation.py index a6a5f666cd..2eaf9f02bb 100644 --- a/panel/pane/equation.py +++ b/panel/pane/equation.py @@ -67,7 +67,7 @@ def applies(cls, obj: Any) -> float | bool | None: else: return False - def _get_model_type(self, root: Model, comm: Comm | None) -> type[Model]: + def _get_model_type(self, root: Model | None, comm: Comm | None) -> type[Model]: module = self.renderer if module is None: if 'panel.models.mathjax' in sys.modules and 'panel.models.katex' not in sys.modules: diff --git a/panel/pane/ipywidget.py b/panel/pane/ipywidget.py index 121999b4db..2d27fdec06 100644 --- a/panel/pane/ipywidget.py +++ b/panel/pane/ipywidget.py @@ -86,7 +86,7 @@ class IPyLeaflet(IPyWidget): 'fixed', 'stretch_width', 'stretch_height', 'stretch_both', 'scale_width', 'scale_height', 'scale_both', None]) - priority: float | bool | None = 0.7 + priority: ClassVar[float | bool | None] = 0.7 @classmethod def applies(cls, obj: Any) -> float | bool | None: @@ -123,7 +123,7 @@ def _get_ipywidget( return super()._get_ipywidget(widget, doc, root, comm, **kwargs) -_ipywidget_classes = {} +_ipywidget_classes: dict[str, type[param.Parameterized]] = {} def _ipywidget_transform(obj): """ diff --git a/panel/pane/markup.py b/panel/pane/markup.py index cbccd26a55..17640d7e3f 100644 --- a/panel/pane/markup.py +++ b/panel/pane/markup.py @@ -270,8 +270,8 @@ def _transform_object(self, obj: Any) -> dict[str, Any]: if 'dask' in module: html = obj.to_html(max_rows=self.max_rows).replace('border="1"', '') elif 'style' in module: - classes = ' '.join(classes) - html = obj.to_html(table_attributes=f'class="{classes}"') + class_string = ' '.join(classes) + html = obj.to_html(table_attributes=f'class="{class_string}"') else: kwargs = {p: getattr(self, p) for p in self._rerender_params if p not in HTMLBasePane.param and p not in ('_object', 'text_align')} @@ -460,7 +460,7 @@ def _transform_object(self, obj: Any) -> dict[str, Any]: html = markdown.markdown( obj, extensions=self.extensions, - output_format='html5', + output_format='xhtml', **self.renderer_options ) else: @@ -511,7 +511,7 @@ class JSON(HTMLBasePane): _applies_kw: ClassVar[bool] = True - _bokeh_model: ClassVar[Model] = _BkJSON + _bokeh_model: ClassVar[type[Model]] = _BkJSON _rename: ClassVar[Mapping[str, str | None]] = { "object": "text", "encoder": None, "style": "styles" diff --git a/panel/pane/media.py b/panel/pane/media.py index 8dbc05b411..4777aac43c 100644 --- a/panel/pane/media.py +++ b/panel/pane/media.py @@ -90,7 +90,7 @@ def applies(cls, obj: Any) -> float | bool | None: return True return False - def _to_np_int16(self, data: np.ndarray): + def _to_np_int16(self, data: np.ndarray) -> np.ndarray: dtype = data.dtype if dtype in (np.float32, np.float64): @@ -98,10 +98,12 @@ def _to_np_int16(self, data: np.ndarray): return data - def _to_buffer(self, data: np.ndarray|TensorLike): - if isinstance(data, TensorLike): - data = data.numpy() - data = self._to_np_int16(data) + def _to_buffer(self, data: np.ndarray | TensorLike): + if isinstance(data, np.ndarray): + values = data + elif isinstance(data, TensorLike): + values = data.numpy() + data = self._to_np_int16(values) from scipy.io import wavfile buffer = BytesIO() diff --git a/panel/pane/plotly.py b/panel/pane/plotly.py index a107d0ef33..0fc2cec3c1 100644 --- a/panel/pane/plotly.py +++ b/panel/pane/plotly.py @@ -380,7 +380,7 @@ def _update(self, ref: str, model: Model) -> None: except Exception: update_frames = True - updates = {} + updates: dict[str, Any] = {} if self.sizing_mode is self.param.sizing_mode.default and 'autosize' in layout: autosize = layout.get('autosize') styles = dict(model.styles) diff --git a/panel/util/__init__.py b/panel/util/__init__.py index 00c901462f..9f1d3ece7f 100644 --- a/panel/util/__init__.py +++ b/panel/util/__init__.py @@ -377,7 +377,7 @@ def parse_timedelta(time_str: str) -> dt.timedelta | None: return dt.timedelta(**time_params) -def fullpath(path: AnyStr | os.PathLike) -> AnyStr | os.PathLike: +def fullpath(path: AnyStr | os.PathLike) -> AnyStr: """Expanduser and then abspath for a given path """ return os.path.abspath(os.path.expanduser(path)) diff --git a/panel/widgets/button.py b/panel/widgets/button.py index b87a0abc73..e2cfe14228 100644 --- a/panel/widgets/button.py +++ b/panel/widgets/button.py @@ -72,7 +72,7 @@ class IconMixin(Widget): __abstract = True def __init__(self, **params) -> None: - self._rename = dict(self._rename, **IconMixin._rename) + type(self)._rename = dict(self._rename, **IconMixin._rename) super().__init__(**params) def _process_param_change(self, params): @@ -289,7 +289,7 @@ class Toggle(_ButtonBase, IconMixin): 'value': 'active', 'name': 'label', } - _supports_embed: ClassVar[bool] = True + _supports_embed: bool = True _widget_type: ClassVar[type[Model]] = _BkToggle diff --git a/panel/widgets/file_selector.py b/panel/widgets/file_selector.py index d8bc561ac9..6e3f3e8c47 100644 --- a/panel/widgets/file_selector.py +++ b/panel/widgets/file_selector.py @@ -137,8 +137,8 @@ def __init__(self, directory: AnyStr | os.PathLike | None = None, **params): self.link(self._selector, size='size') # Set up state - self._stack = [] - self._cwd = None + self._stack: list[str] = [] + self._cwd: str | None = None self._position = -1 self._update_files(True) @@ -203,7 +203,7 @@ def _update_files( self, event: param.parameterized.Event | None = None, refresh: bool = False ): path = fullpath(self._directory.value) - refresh = refresh or (event and getattr(event, 'obj', None) is self._reload) + refresh = bool(refresh or (event and getattr(event, 'obj', None) is self._reload)) if refresh: path = self._cwd elif not os.path.isdir(path): diff --git a/panel/widgets/indicators.py b/panel/widgets/indicators.py index 67406237cc..59d74f9055 100644 --- a/panel/widgets/indicators.py +++ b/panel/widgets/indicators.py @@ -54,7 +54,7 @@ try: from tqdm.asyncio import tqdm as _tqdm except ImportError: - _tqdm = None + _tqdm = None # type: ignore RED = "#d9534f" GREEN = "#5cb85c" @@ -1221,7 +1221,7 @@ def _process_param_change(self, msg): -class ptqdm(_tqdm or object): +class ptqdm(_tqdm or object): # type: ignore def __init__(self, *args, **kwargs): if _tqdm is None: diff --git a/panel/widgets/input.py b/panel/widgets/input.py index db339f22d5..4b745ceb8e 100644 --- a/panel/widgets/input.py +++ b/panel/widgets/input.py @@ -408,8 +408,8 @@ def _process_event(self, event: DeleteEvent | UploadEvent): return buffers = self._file_buffer.pop(name) - file_buffer = b''.join(buffers) - if data['type'].startswith('text/'): + file_buffer: bytes | str = b''.join(buffers) + if data['type'].startswith('text/') and isinstance(file_buffer, bytes): try: file_buffer = file_buffer.decode('utf-8') except UnicodeDecodeError: @@ -1487,7 +1487,7 @@ class _BooleanWidget(Widget): value = param.Boolean(default=False, doc=""" The current value""") - _supports_embed: ClassVar[bool] = True + _supports_embed: bool = True _rename: ClassVar[Mapping[str, str | None]] = {'value': 'active', 'name': 'label'} diff --git a/panel/widgets/player.py b/panel/widgets/player.py index 5b16a9bc8d..6d633d683f 100644 --- a/panel/widgets/player.py +++ b/panel/widgets/player.py @@ -120,7 +120,7 @@ class Player(PlayerBase): value_throttled = param.Integer(default=0, constant=True, doc=""" Current throttled player value.""") - _supports_embed: ClassVar[bool] = True + _supports_embed: bool = True def __init__(self, **params): if 'length' in params: diff --git a/panel/widgets/select.py b/panel/widgets/select.py index d93922b367..ba8bdfbb95 100644 --- a/panel/widgets/select.py +++ b/panel/widgets/select.py @@ -80,7 +80,7 @@ class SingleSelectBase(SelectBase): _allows_none: ClassVar[bool] = False - _supports_embed: ClassVar[bool] = True + _supports_embed: bool = True __abstract = True @@ -275,7 +275,7 @@ def _process_param_change(self, msg: dict[str, Any]) -> dict[str, Any]: groups_provided = 'groups' in msg msg = super()._process_param_change(msg) if groups_provided or 'options' in msg and self.groups: - groups = self.groups + groups: dict[str, list[str | tuple[str, str]]] = self.groups if (all(isinstance(values, dict) for values in groups.values()) is False and all(isinstance(values, list) for values in groups.values()) is False): raise ValueError( @@ -737,7 +737,7 @@ class _MultiSelectBase(SingleSelectBase): description = param.String(default=None, doc=""" An HTML string describing the function of this component.""") - _supports_embed: ClassVar[bool] = False + _supports_embed: bool = False __abstract = True @@ -1049,7 +1049,7 @@ class RadioButtonGroup(_RadioGroupBase, _ButtonBase, TooltipMixin): 'value': "source.labels[value]", 'button_style': None, 'description': None } - _supports_embed: ClassVar[bool] = True + _supports_embed: bool = True _widget_type: ClassVar[type[Model]] = _BkRadioButtonGroup @@ -1077,7 +1077,7 @@ class RadioBoxGroup(_RadioGroupBase): Whether the items be arrange vertically (``False``) or horizontally in-line (``True``).""") - _supports_embed: ClassVar[bool] = True + _supports_embed: bool = True _widget_type: ClassVar[type[Model]] = _BkRadioBoxGroup