holoviz/panel · #8777

Drop pyodide-http from converted apps

philippjfr · merged Sep 27, 20269 files · 36 + / 66 −
doc/conf.py1 + / 1 −
@@ -227,7 +227,7 @@ def get_requirements(): nbsite_pyodide_conf = {     'PYODIDE_URL': PYODIDE_MODULE_URL,     'lockfile': True,-    'requirements': [bokeh_req, panel_req, 'pyodide-http'],+    'requirements': [bokeh_req, panel_req],     'requires': get_requirements(), } 
doc/how_to/wasm/convert.md1 + / 2 −
@@ -23,7 +23,6 @@ The ``panel convert`` command has the following options:                           Explicit requirements to add to the converted file, a single requirements.txt file or a JSON file containing requirements per app. By default requirements are inferred from the code.     --resources RESOURCES [RESOURCES ...]                           Files to pack for distribution with the app. Does only support files located in the directory of the main panel app (or in subdirectories below).-    --disable-http-patch  Whether to disable patching http requests using the pyodide-http library.     --watch               Watch the files     --num-procs NUM_PROCS                           The number of processes to start in parallel to convert the apps.@@ -146,6 +145,6 @@ If you decide to enable the `--pwa` ensure that you also provide a unique `--tit  ## Handling HTTP requests -By default Panel will install the [pyodide-http](https://github.com/koenvo/pyodide-http) library which patches `urllib3` and `requests` making it possible to use them within the pyodide process. To disable this behavior use the `--disable-http-patch` CLI option.+Recent Pyodide releases support `urllib3` and `requests` without an additional HTTP patch. Panel uses Pyodide's HTTP API to load URLs passed to `pandas.read_csv` and `pandas.read_json`. Browser security rules, including CORS, still apply.  Note that making HTTP requests when converting to the `pyodide` or `pyscript` target will block the main browser thread and result in a poor user experience. Therefore we strongly recommend converting to `pyodide-worker` if your app is making synchronous HTTP requests.
doc/pyodide_dependencies.json2 + / 2 −
@@ -4,7 +4,7 @@   "gallery/glaciers.ipynb": ["holoviews", "colorcet", "hvplot"],   "gallery/hvplot_explorer.ipynb": ["hvplot", "scipy"],   "gallery/iris_kmeans.ipynb": ["hvplot", "scikit-learn", "bokeh_sampledata"],-  "gallery/nyc_deckgl.ipynb": ["fastparquet", "pyodide-http"],+  "gallery/nyc_deckgl.ipynb": ["fastparquet"],   "gallery/penguin_crossfilter.ipynb": ["hvplot"],   "gallery/penguin_kmeans.ipynb": ["altair"],   "gallery/portfolio_optimizer.ipynb": ["hvplot", "scipy"],@@ -95,7 +95,7 @@   "reference/panes/Textual.ipynb": null,   "reference/panes/Vega.ipynb": ["altair", "vega_datasets"],   "reference/panes/VTK.ipynb": ["vtk", "pyvista"],-  "reference/panes/VTKJS.ipynb": ["pyodide-http"],+  "reference/panes/VTKJS.ipynb": [],   "reference/panes/VTKVolume.ipynb": ["pyvista"],   "reference/templates/Bootstrap.ipynb": ["holoviews"],   "reference/templates/FastGridTemplate.ipynb": ["holoviews"],
panel/command/convert.py0 + / 6 −
@@ -74,11 +74,6 @@ class Convert(Subcommand):                 "JSON file containing requirements per app. By default requirements are inferred from the code."             )         )),-        ('--disable-http-patch', Argument(-            default = False,-            action  = 'store_true',-            help    = "Whether to disable patching http requests using the pyodide-http library."-        )),         ('--resources', Argument(             nargs   = '+',             help    = (@@ -147,7 +142,6 @@ def invoke(self, args: argparse.Namespace) -> None:                     build_pwa=args.pwa,                     title=args.title,                     max_workers=args.num_procs,-                    http_patch=not args.disable_http_patch,                     compiled=args.compiled,                     verbose=True                 )
panel/io/convert.py1 + / 13 −
@@ -180,7 +180,6 @@ def collect_python_requirements(     code: str | os.PathLike | t.IO,     requirements: list[str] | t.Literal['auto'] | os.PathLike = 'auto',     panel_version: t.Literal['auto', 'local'] | str = 'auto',-    http_patch: bool = True, ) -> list[str]:     """     Make sense of python requirements for our Panel script.@@ -193,9 +192,6 @@ def collect_python_requirements(         The list of requirements to include (in addition to Panel).     panel_version: Literal['auto', 'local'] | str         The panel release version to use in the exported HTML.-    http_patch: bool-        Whether to patch the HTTP request stack with the pyodide-http library-        to allow urllib3 and requests to work.     """     # Environment     if panel_version == 'local':@@ -208,8 +204,6 @@ def collect_python_requirements(         panel_req = f'panel=={panel_version}'         bokeh_req = f'bokeh=={BOKEH_VERSION}'     collected_requirements = [bokeh_req, panel_req]-    if http_patch:-        collected_requirements.append('pyodide-http')      requirements_root = os.getcwd()     resolved_reqs: list[str]@@ -505,7 +499,6 @@ def convert_app(     manifest: str | None = None,     panel_version: t.Literal['auto', 'local'] | str = 'auto',     local_prefix: str = LOCAL_PREFIX,-    http_patch: bool = True,     inline: bool = False,     compiled: bool = False,     verbose: bool = True,@@ -520,7 +513,7 @@ def convert_app(      # Obtain source     parsed_requirements = collect_python_requirements(-        app, requirements, panel_version=panel_version, http_patch=http_patch+        app, requirements, panel_version=panel_version     )     # prepare wheels to be available via emscripten MEMFS     parsed_requirements_rewritten = []@@ -647,7 +640,6 @@ def convert_apps(     max_workers: int = 4,     panel_version: t.Literal['auto', 'local'] | str = 'auto',     local_prefix: str = LOCAL_PREFIX,-    http_patch: bool = True,     inline: bool = False,     compiled: bool = False,     verbose: bool = True,@@ -688,9 +680,6 @@ def convert_apps( '       The panel version to include.     local_prefix: str         Prefix for the path to serve local wheel files from.-    http_patch: bool-        Whether to patch the HTTP request stack with the pyodide-http library-        to allow urllib3 and requests to work.     inline: bool         Whether to inline resources.     compiled: bool@@ -724,7 +713,6 @@ def convert_apps(         'prerender': prerender,         'manifest': manifest,         'panel_version': panel_version,-        'http_patch': http_patch,         'inline': inline,         'verbose': verbose,         'compiled': compiled,
panel/io/pyodide.py23 + / 31 −
@@ -12,6 +12,7 @@  import bokeh import js+import pandas import param  import pyodide # isort: split@@ -76,12 +77,6 @@ if _IN_WORKER:     os.environ['MPLBACKEND'] = 'agg' -try:-    import pyodide_http-    pyodide_http.patch_all()-except Exception:-    pyodide_http = None- try:     # Patch fsspec with synchronous http support     import fsspec.implementations.http_sync  # noqa@@ -101,31 +96,28 @@ def _sampledata_dir(create=None):         return '/drive/assets/sampledata'     bokeh.util.sampledata.external_data_dir = _sampledata_dir -if pyodide_http is None:-    import pandas--    def _read_file(*args, **kwargs):-        if args and isurl(args[0]):-            args = (pyodide.http.open_url(args[0]),)+args[1:]-        elif isurl(kwargs.get('filepath_or_buffer')):-            kwargs['filepath_or_buffer'] = pyodide.http.open_url(kwargs['filepath_or_buffer'])-        return args, kwargs--    # Patch pandas.read_csv-    _read_csv_original = pandas.read_csv-    @functools.wraps(pandas.read_csv)-    def _read_csv(*args, **kwargs):-        args, kwargs = _read_file(*args, **kwargs)-        return _read_csv_original(*args, **kwargs)-    pandas.read_csv = _read_csv  # type: ignore--    # Patch pandas.read_json-    _read_json_original = pandas.read_json-    @functools.wraps(pandas.read_json)-    def _read_json(*args, **kwargs):-        args, kwargs = _read_file(*args, **kwargs)-        return _read_json_original(*args, **kwargs)-    pandas.read_json = _read_json  # type: ignore+def _read_file(*args, **kwargs):+    if args and isurl(args[0]):+        args = (pyodide.http.open_url(args[0]),)+args[1:]+    elif isurl(kwargs.get('filepath_or_buffer')):+        kwargs['filepath_or_buffer'] = pyodide.http.open_url(kwargs['filepath_or_buffer'])+    return args, kwargs++# Patch pandas.read_csv+_read_csv_original = pandas.read_csv+@functools.wraps(pandas.read_csv)+def _read_csv(*args, **kwargs):+    args, kwargs = _read_file(*args, **kwargs)+    return _read_csv_original(*args, **kwargs)+pandas.read_csv = _read_csv  # type: ignore++# Patch pandas.read_json+_read_json_original = pandas.read_json+@functools.wraps(pandas.read_json)+def _read_json(*args, **kwargs):+    args, kwargs = _read_file(*args, **kwargs)+    return _read_json_original(*args, **kwargs)+pandas.read_json = _read_json  # type: ignore  _tasks: set = set() 
panel/tests/io/test_mime_render.py5 + / 0 −
@@ -1,5 +1,6 @@ import pathlib +from panel.io.convert import BOKEH_VERSION, collect_python_requirements from panel.io.mime_render import (     WriteCallbackStream, exec_with_return, find_requirements, format_mime, )@@ -28,6 +29,10 @@ def _repr_png_(self):         with open(pathlib.Path(__file__).parent.parent / 'test_data' / 'logo.png', 'rb') as f:             return f.read() +def test_convert_requirements_use_native_http():+    requirements = collect_python_requirements('', ['requests'], panel_version='1.10.0')+    assert requirements == [f'bokeh=={BOKEH_VERSION}', 'panel==1.10.0', 'requests']+ def test_find_imports_stdlibs():     code = """     import os
panel/tests/ui/io/test_convert.py3 + / 10 −
@@ -267,16 +267,9 @@ def test_pyodide_test_convert_tabulator_app(http_serve, page, runtime):     assert [msg for msg in msgs if msg.type == 'error' and 'favicon' not in msg.location['url']] == []  -@pytest.mark.parametrize(-    'runtime, http_patch', [-        ('pyodide', False),-        ('pyodide', True),-        ('pyodide-worker', False),-        ('pyodide-worker', True)-    ]-)-def test_pyodide_test_convert_csv_app(http_serve, page, runtime, http_patch):-    msgs = wait_for_app(http_serve, csv_app, page, runtime, http_patch=http_patch)+@pytest.mark.parametrize('runtime', ['pyodide', 'pyodide-worker'])+def test_pyodide_test_convert_csv_app(http_serve, page, runtime):+    msgs = wait_for_app(http_serve, csv_app, page, runtime)      expected_titles = ['index', 'date', 'Temperature', 'Humidity', 'Light', 'CO2', 'HumidityRatio', 'Occupancy'] 
pyproject.toml0 + / 1 −
@@ -298,7 +298,6 @@ module = [     "pydeck.*",     "pyarrow.*",     "pyecharts.*",-    "pyodide_http.*",     "pyodide.*",     "pyscript.*",     "pyvista.*",