Skip to content

Commit

Permalink
More types
Browse files Browse the repository at this point in the history
  • Loading branch information
philippjfr committed Nov 16, 2024
1 parent 9e602c9 commit 396ad6d
Show file tree
Hide file tree
Showing 48 changed files with 392 additions and 298 deletions.
4 changes: 2 additions & 2 deletions panel/command/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class Convert(Subcommand):
)),
('--compiled', Argument(
default = False,
action = 'store_false',
action = 'store_true',
help = "Whether to use the compiled and faster version of Pyodide."
)),
('--out', Argument(
Expand Down Expand Up @@ -75,7 +75,7 @@ class Convert(Subcommand):
)),
('--disable-http-patch', Argument(
default = False,
action = 'store_false',
action = 'store_true',
help = "Whether to disable patching http requests using the pyodide-http library."
)),
('--watch', Argument(
Expand Down
46 changes: 25 additions & 21 deletions panel/custom.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
if TYPE_CHECKING:
from bokeh.document import Document
from bokeh.events import Event
from bokeh.model import Model
from bokeh.model import Model, UIElement
from pyviz_comms import Comm

ExportSpec = dict[str, list[str | tuple[str, ...]]]
Expand Down Expand Up @@ -152,7 +152,7 @@ def _get_model(
return model

def select(
self, selector: Optional[type | Callable[Viewable, bool]] = None
self, selector: type | Callable[[Viewable], bool] | None = None
) -> list[Viewable]:
return super().select(selector) + self._view__.select(selector)

Expand Down Expand Up @@ -250,19 +250,19 @@ def _module_path(cls):
@classproperty
def _bundle_path(cls) -> os.PathLike | None:
if config.autoreload and cls._esm:
return
return None
mod_path = cls._module_path
if mod_path is None:
return
return None
if cls._bundle:
for scls in cls.__mro__:
if issubclass(scls, ReactiveESM) and cls._bundle == scls._bundle:
cls = scls
mod_path = cls._module_path
bundle = cls._bundle
if isinstance(bundle, pathlib.PurePath):
if isinstance(bundle, os.PathLike):
return bundle
elif bundle.endswith('.js'):
elif bundle and bundle.endswith('.js'):
bundle_path = mod_path / bundle
if bundle_path.is_file():
return bundle_path
Expand All @@ -286,16 +286,18 @@ def _bundle_path(cls) -> os.PathLike | None:
mod = importlib.import_module(submodule)
except (ModuleNotFoundError, ImportError):
continue
if not hasattr(mod, '__file__'):
mod_file = getattr(mod, '__file__', None)
if not mod_file:
continue
submodule_path = pathlib.Path(mod.__file__).parent
submodule_path = pathlib.Path(mod_file).parent
path = submodule_path / f'{submodule}.bundle.js'
if path.is_file():
return path

if module in sys.modules:
module = os.path.basename(sys.modules[module].__file__).replace('.py', '')
path = mod_path / f'{module}.bundle.js'
# Get module name from the module
module_obj = sys.modules[module]
path = mod_path / f'{module_obj.__name__}.bundle.js'
return path if path.is_file() else None
return None

Expand All @@ -306,10 +308,10 @@ def _esm_path(cls, compiled: bool = True) -> os.PathLike | None:
if bundle_path:
return bundle_path
esm = cls._esm
if isinstance(esm, pathlib.PurePath):
if isinstance(esm, os.PathLike):
return esm
elif not esm.endswith(('.js', '.jsx', '.ts', '.tsx')):
return
elif not esm or not esm.endswith(('.js', '.jsx', '.ts', '.tsx')):
return None
try:
if hasattr(cls, '__path__'):
mod_path = cls.__path__
Expand All @@ -320,7 +322,7 @@ def _esm_path(cls, compiled: bool = True) -> os.PathLike | None:
return esm_path
except (OSError, TypeError, ValueError):
pass
return
return None

@classmethod
def _render_esm(cls, compiled: bool | Literal['compiling'] = True, server: bool = False):
Expand All @@ -344,7 +346,7 @@ def _render_esm(cls, compiled: bool | Literal['compiling'] = True, server: bool
esm = textwrap.dedent(esm)
return esm

def _cleanup(self, root: Model | None) -> None:
def _cleanup(self, root: Model | None = None) -> None:
if root:
ref = root.ref['id']
if ref in self._models:
Expand Down Expand Up @@ -382,10 +384,10 @@ def _update_esm(self):
self._apply_update({}, {'esm': esm}, model, ref)

@property
def _linked_properties(self) -> tuple[str]:
def _linked_properties(self) -> tuple[str, ...]:
return tuple(p for p in self._data_model.properties() if p not in ('js_property_callbacks',))

def _get_properties(self, doc: Document) -> dict[str, Any]:
def _get_properties(self, doc: Document | None) -> dict[str, Any]:
props = super()._get_properties(doc)
cls = type(self)
data_params = {}
Expand All @@ -411,7 +413,7 @@ def _get_properties(self, doc: Document) -> dict[str, Any]:
importmap = self._process_importmap()
is_session = False
if bundle_path:
is_session = (doc.session_context and doc.session_context.server_context)
is_session = bool(doc and doc.session_context and doc.session_context.server_context)
if bundle_path == self._esm_path(not config.autoreload) and cls.__module__ in sys.modules and is_session:
bundle_hash = 'url'
else:
Expand All @@ -435,7 +437,9 @@ def _get_properties(self, doc: Document) -> dict[str, Any]:
def _process_importmap(cls):
return cls._importmap

def _get_child_model(self, child, doc, root, parent, comm):
def _get_child_model(
self, child: Viewable, doc: Document, root: Model, parent: Model, comm: Comm | None
) -> list[UIElement] | UIElement | None:
if child is None:
return None
ref = root.ref['id']
Expand All @@ -448,7 +452,7 @@ def _get_child_model(self, child, doc, root, parent, comm):
return child._models[ref][0]
return child._get_model(doc, root, parent, comm)

def _get_children(self, data_model, doc, root, parent, comm):
def _get_children(self, data_model, doc, root, parent, comm) -> dict[str, list[UIElement] | UIElement | None]:
children = {}
for k, v in self.param.values().items():
p = self.param[k]
Expand All @@ -475,7 +479,7 @@ def _get_model(
root = root or model
children = self._get_children(model.data, doc, root, model, comm)
model.data.update(**children)
model.children = list(children)
model.children = list(children) # type: ignore
self._models[root.ref['id']] = (model, parent)
self._link_props(model.data, self._linked_properties, doc, root, comm)
self._register_events('dom_event', 'data_event', model=model, doc=doc, comm=comm)
Expand Down
9 changes: 8 additions & 1 deletion panel/io/admin.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
from __future__ import annotations

import datetime as dt
import logging
import os
import sys
import time

from functools import partial
from typing import TYPE_CHECKING

import bokeh
import numpy as np
Expand Down Expand Up @@ -34,7 +37,11 @@
from .server import set_curdoc
from .state import state

PROCESSES = {}
if TYPE_CHECKING:
from psutil import Process


PROCESSES: dict[int, Process] = {}

log_sessions = []

Expand Down
28 changes: 17 additions & 11 deletions panel/io/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from functools import partial
from types import FunctionType, MethodType
from typing import (
TYPE_CHECKING, Any, Callable, Mapping, TypeAlias,
TYPE_CHECKING, Any, Callable, Sequence, TypeAlias,
)
from urllib.parse import urljoin

Expand Down Expand Up @@ -48,7 +48,7 @@


def _eval_panel(
panel: TViewableFuncOrPath, server_id: str, title: str,
panel: TViewableFuncOrPath, server_id: str | None, title: str,
location: bool | Location, admin: bool, doc: Document
):
from ..pane import panel as as_panel
Expand Down Expand Up @@ -173,7 +173,11 @@ def process_request(self, request) -> dict[str, Any]:
if user and config.cookie_secret:
from tornado.web import decode_signed_value
try:
user = decode_signed_value(config.cookie_secret, 'user', user.value).decode('utf-8')
decoded = decode_signed_value(config.cookie_secret, 'user', user.value)
if decoded:
user = decoded.decode('utf-8')
else:
user = user.value
except Exception:
user = user.value
if user in state._oauth_user_overrides:
Expand All @@ -186,7 +190,7 @@ def process_request(self, request) -> dict[str, Any]:
bokeh.command.util.Application = Application # type: ignore


def build_single_handler_application(path, argv=None):
def build_single_handler_application(path: str | os.PathLike, argv=None) -> Application:
argv = argv or []
path = os.path.abspath(os.path.expanduser(path))
handler: Handler
Expand Down Expand Up @@ -219,13 +223,13 @@ def build_single_handler_application(path, argv=None):


def build_applications(
panel: TViewableFuncOrPath | Mapping[str, TViewableFuncOrPath],
panel: TViewableFuncOrPath | dict[str, TViewableFuncOrPath],
title: str | dict[str, str] | None = None,
location: bool | Location = True,
admin: bool = False,
server_id: str | None = None,
custom_handlers: list | None = None
) -> dict[str, Application]:
custom_handlers: Sequence[Callable[[str, TViewableFuncOrPath], TViewableFuncOrPath]] | None = None
) -> dict[str, BkApplication]:
"""
Converts a variety of objects into a dictionary of Applications.
Expand All @@ -248,7 +252,7 @@ def build_applications(
if not isinstance(panel, dict):
panel = {'/': panel}

apps = {}
apps: dict[str, BkApplication] = {}
for slug, app in panel.items():
if slug.endswith('/') and slug != '/':
raise ValueError(f"Invalid URL: trailing slash '/' used for {slug!r} not supported.")
Expand All @@ -260,13 +264,15 @@ def build_applications(
"Keys of the title dictionary and of the apps "
f"dictionary must match. No {slug} key found in the "
"title dictionary.") from None
else:
elif title:
title_ = title
else:
title_ = 'Panel Application'
slug = slug if slug.startswith('/') else '/'+slug

# Handle other types of apps using a custom handler
for handler in (custom_handlers or ()):
new_app = handler(slug, app)
for chandler in (custom_handlers or ()):
new_app = chandler(slug, app)
if app is not None:
break
else:
Expand Down
30 changes: 16 additions & 14 deletions panel/io/convert.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
PYODIDE_PYC_JS = f'<script src="{PYODIDE_PYC_URL}"></script>'
LOCAL_PREFIX = './'

MINIMUM_VERSIONS = {}
MINIMUM_VERSIONS: dict[str, str] = {}

ICON_DIR = DIST_DIR / 'images'
PWA_IMAGES = [
Expand Down Expand Up @@ -150,7 +150,7 @@ def build_pwa_manifest(files, title=None, **kwargs) -> str:

def script_to_html(
filename: str | os.PathLike | IO,
requirements: Literal['auto'] | list[str] = 'auto',
requirements: list[str] | Literal['auto'] | os.PathLike = 'auto',
js_resources: Literal['auto'] | list[str] = 'auto',
css_resources: Literal['auto'] | list[str] | None = 'auto',
runtime: Runtimes = 'pyodide',
Expand All @@ -161,7 +161,7 @@ def script_to_html(
http_patch: bool = True,
inline: bool = False,
compiled: bool = True
) -> str:
) -> tuple[str, str | None]:
"""
Converts a Panel or Bokeh script to a standalone WASM Python
application.
Expand Down Expand Up @@ -202,7 +202,7 @@ def script_to_html(
app_name = '.'.join(path.name.split('.')[:-1])
app = build_single_handler_application(str(path.absolute()))
document = Document()
document._session_context = lambda: MockSessionContext(document=document)
document._session_context = lambda: MockSessionContext(document=document) # type: ignore
with set_curdoc(document):
app.initialize_document(document)
state._on_load(None)
Expand Down Expand Up @@ -416,11 +416,9 @@ def convert_app(

with open(dest_path / filename, 'w', encoding="utf-8") as out:
out.write(html)
if runtime == 'pyscript-worker':
with open(dest_path / f'{name}.py', 'w', encoding="utf-8") as out:
out.write(worker)
elif runtime == 'pyodide-worker':
with open(dest_path / f'{name}.js', 'w', encoding="utf-8") as out:
if 'worker' in runtime and worker:
ext = 'py' if runtime.startswith('pyscript') else 'js'
with open(dest_path / f'{name}.{ext}', 'w', encoding="utf-8") as out:
out.write(worker)
if verbose:
print(f'Successfully converted {app} to {runtime} target and wrote output to {filename}.')
Expand Down Expand Up @@ -545,15 +543,19 @@ def convert_apps(
app_requirements = requirements

kwargs = {
'runtime': runtime, 'prerender': prerender,
'manifest': manifest, 'panel_version': panel_version,
'http_patch': http_patch, 'inline': inline,
'verbose': verbose, 'compiled': compiled,
'runtime': runtime,
'prerender': prerender,
'manifest': manifest,
'panel_version': panel_version,
'http_patch': http_patch,
'inline': inline,
'verbose': verbose,
'compiled': compiled,
'local_prefix': local_prefix
}

if state._is_pyodide:
files = dict(convert_app(app, dest_path, requirements=app_requirements, **kwargs) for app in apps)
files = {app: convert_app(app, dest_path, requirements=app_requirements, **kwargs) for app in apps}
else:
files = _convert_process_pool(
apps, dest_path, max_workers=max_workers, requirements=app_requirements, **kwargs
Expand Down
5 changes: 3 additions & 2 deletions panel/io/datamodel.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import weakref
from __future__ import annotations

from functools import partial
from weakref import WeakKeyDictionary

import bokeh
import bokeh.core.properties as bp
Expand Down Expand Up @@ -53,7 +54,7 @@ def validate(self, value, detail=True):
raise ValueError(msg)


_DATA_MODELS = weakref.WeakKeyDictionary()
_DATA_MODELS: WeakKeyDictionary[type[pm.Parameterized], type[DataModel]] = WeakKeyDictionary()

# The Bokeh Color property has `_default_help` set which causes
# an error to be raise when Nullable is called on it. This converter
Expand Down
Loading

0 comments on commit 396ad6d

Please sign in to comment.