holoviz/panel · #8777
Drop pyodide-http from converted apps
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 ospanel/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.*",