panel icon indicating copy to clipboard operation
panel copied to clipboard

Document potential caching issues when using pyodide worker convert target

Open brentgunderson opened this issue 1 year ago • 7 comments

Regarding this issue: https://github.com/holoviz/panel/issues/5371

I tried with panel 1.3.6 and seem to encounter the same error.

This from the web browser console:

[Log] PythonError: Traceback (most recent call last): (script.js, line 33)
  File "micropip/_micropip.py", line 160, in _fetch_bytes
  File "micropip/_compat_in_pyodide.py", line 29, in fetch_bytes
  File "pyodide/http.py", line 201, in bytes
  File "pyodide/http.py", line 125, in _raise_if_failed
OSError: Request for https://cdn.holoviz.org/panel/1.2.1/dist/wheels/bokeh-3.1.1-py3-none-any.whl failed with status 403: 

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "_pyodide/_base.py", line 540, in eval_code_async
  File "_pyodide/_base.py", line 365, in run_async
  File "<exec>", line 3, in <module>
  File "micropip/_micropip.py", line 576, in install
  File "micropip/_micropip.py", line 342, in gather_requirements
  File "micropip/_micropip.py", line 355, in add_requirement
  File "micropip/_micropip.py", line 470, in add_wheel
  File "micropip/_micropip.py", line 176, in download
  File "micropip/_micropip.py", line 168, in _fetch_bytes
ValueError: Can't fetch wheel from 'https://cdn.holoviz.org/panel/1.2.1/dist/wheels/bokeh-3.1.1-py3-none-any.whl'. One common reason for this is when the server blocks Cross-Origin Resource Sharing (CORS). Check if the server is sending the correct 'Access-Control-Allow-Origin' header.

[Log] PythonError: Traceback (most recent call last): (script.js, line 33)
  File "_pyodide/_base.py", line 540, in eval_code_async
  File "_pyodide/_base.py", line 365, in run_async
  File "<exec>", line 3, in <module>
  File "micropip/_micropip.py", line 576, in install
  File "micropip/_micropip.py", line 342, in gather_requirements
  File "micropip/_micropip.py", line 355, in add_requirement
  File "micropip/_micropip.py", line 472, in add_wheel
  File "micropip/_micropip.py", line 342, in gather_requirements
  File "micropip/_micropip.py", line 346, in add_requirement
  File "micropip/_micropip.py", line 457, in add_requirement_inner
  File "micropip/_micropip.py", line 472, in add_wheel
  File "micropip/_micropip.py", line 342, in gather_requirements
  File "micropip/_micropip.py", line 346, in add_requirement
  File "micropip/_micropip.py", line 444, in add_requirement_inner
  File "micropip/_micropip.py", line 312, in find_wheel
ValueError: Can't find a pure Python 3 wheel for 'contourpy>=1'.
See: https://pyodide.org/en/stable/usage/faq.html#micropip-can-t-find-a-pure-python-wheel
You can use `micropip.install(..., keep_going=True)`to get a list of all packages with missing wheels.

[Log] PythonError: Traceback (most recent call last): (script.js, line 33)
  File "_pyodide/_base.py", line 540, in eval_code_async
  File "_pyodide/_base.py", line 365, in run_async
  File "<exec>", line 3, in <module>
  File "micropip/_micropip.py", line 576, in install
  File "micropip/_micropip.py", line 342, in gather_requirements
  File "micropip/_micropip.py", line 349, in add_requirement
  File "micropip/_micropip.py", line 457, in add_requirement_inner
  File "micropip/_micropip.py", line 472, in add_wheel
  File "micropip/_micropip.py", line 342, in gather_requirements
  File "micropip/_micropip.py", line 346, in add_requirement
  File "micropip/_micropip.py", line 457, in add_requirement_inner
  File "micropip/_micropip.py", line 472, in add_wheel
  File "micropip/_micropip.py", line 342, in gather_requirements
  File "micropip/_micropip.py", line 346, in add_requirement
  File "micropip/_micropip.py", line 444, in add_requirement_inner
  File "micropip/_micropip.py", line 312, in find_wheel
ValueError: Can't find a pure Python 3 wheel for 'contourpy>=1'.
See: https://pyodide.org/en/stable/usage/faq.html#micropip-can-t-find-a-pure-python-wheel
You can use `micropip.install(..., keep_going=True)`to get a list of all packages with missing wheels.

brentgunderson avatar Jan 18 '24 12:01 brentgunderson

It does not look like you have 1.3.6 since the URL it's trying to fetch from is:

https://cdn.holoviz.org/panel/1.2.1/dist/wheels/bokeh-3.1.1-py3-none-any.whl

and the 1.2.1 indicates that you have version 1.2.1 installed.

philippjfr avatar Jan 18 '24 13:01 philippjfr

Would suggest running which panel to figure out where it's getting Panel from.

philippjfr avatar Jan 18 '24 13:01 philippjfr

Thanks for the suggestion -- here is what I find:

brentgunderson@Brents-Air panel % which panel
/Users/brentgunderson/.pyenv/shims/panel
brentgunderson@Brents-Air panel % panel --version
1.3.6

Error message (do I need to clear something from browser?):

[Error] Failed to load resource: the server responded with a status of 403 () (bokeh-3.1.1-py3-none-any.whl, line 0)
[Log] PythonError: Traceback (most recent call last): (script.js, line 33)
  File "micropip/_micropip.py", line 160, in _fetch_bytes
  File "micropip/_compat_in_pyodide.py", line 29, in fetch_bytes
  File "pyodide/http.py", line 201, in bytes
  File "pyodide/http.py", line 125, in _raise_if_failed
OSError: Request for https://cdn.holoviz.org/panel/1.2.1/dist/wheels/bokeh-3.1.1-py3-none-any.whl failed with status 403: 

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "_pyodide/_base.py", line 540, in eval_code_async
  File "_pyodide/_base.py", line 365, in run_async
  File "<exec>", line 3, in <module>
  File "micropip/_micropip.py", line 576, in install
  File "micropip/_micropip.py", line 342, in gather_requirements
  File "micropip/_micropip.py", line 355, in add_requirement
  File "micropip/_micropip.py", line 470, in add_wheel
  File "micropip/_micropip.py", line 176, in download
  File "micropip/_micropip.py", line 168, in _fetch_bytes
ValueError: Can't fetch wheel from 'https://cdn.holoviz.org/panel/1.2.1/dist/wheels/bokeh-3.1.1-py3-none-any.whl'. One common reason for this is when the server blocks Cross-Origin Resource Sharing (CORS). Check if the server is sending the correct 'Access-Control-Allow-Origin' header.

Here is pyodide/script.js:

importScripts("https://cdn.jsdelivr.net/pyodide/v0.24.1/full/pyodide.js");

function sendPatch(patch, buffers, msg_id) {
  self.postMessage({
    type: 'patch',
    patch: patch,
    buffers: buffers
  })
}

async function startApplication() {
  console.log("Loading pyodide!");
  self.postMessage({type: 'status', msg: 'Loading pyodide'})
  self.pyodide = await loadPyodide();
  self.pyodide.globals.set("sendPatch", sendPatch);
  console.log("Loaded!");
  await self.pyodide.loadPackage("micropip");
  const env_spec = ['https://cdn.holoviz.org/panel/wheels/bokeh-3.3.2-py3-none-any.whl', 'https://cdn.holoviz.org/panel/1.3.6/dist/wheels/panel-1.3.6-py3-none-any.whl', 'pyodide-http==0.2.1', 'scikit-learn', 'xgboost', 'pandas']
  for (const pkg of env_spec) {
    let pkg_name;
    if (pkg.endsWith('.whl')) {
      pkg_name = pkg.split('/').slice(-1)[0].split('-')[0]
    } else {
      pkg_name = pkg
    }
    self.postMessage({type: 'status', msg: `Installing ${pkg_name}`})
    try {
      await self.pyodide.runPythonAsync(`
        import micropip
        await micropip.install('${pkg}');
      `);
    } catch(e) {
      console.log(e)
      self.postMessage({
	type: 'status',
	msg: `Error while installing ${pkg_name}`
      });
    }
  }
  console.log("Packages loaded!");
  self.postMessage({type: 'status', msg: 'Executing code'})
  const code = `
  
import asyncio

from panel.io.pyodide import init_doc, write_doc

init_doc()

import panel as pn

from sklearn.datasets import load_iris
from sklearn.metrics import accuracy_score
from xgboost import XGBClassifier

pn.extension(sizing_mode="stretch_width", template="fast")
pn.state.template.param.update(site="Panel in the Browser", title="XGBoost Example")

iris_df = load_iris(as_frame=True)

trees = pn.widgets.IntSlider(start=2, end=30, name="Number of trees")


def pipeline(trees):
    model = XGBClassifier(max_depth=2, n_estimators=trees)
    model.fit(iris_df.data, iris_df.target)
    accuracy = round(
        accuracy_score(iris_df.target, model.predict(iris_df.data)) * 100, 1
    )
    return pn.indicators.Number(
        name="Test score",
        value=accuracy,
        format="{value}%",
        colors=[(97.5, "red"), (99.0, "orange"), (100, "green")],
    )


pn.Column(
    "Simple example of training an XGBoost classification model on the small Iris dataset.",
    iris_df.data.head(),
    "Move the slider below to change the number of training rounds for the XGBoost classifier. The training accuracy score will adjust accordingly.",
    trees,
    pn.bind(pipeline, trees),
).servable()


await write_doc()
  `

  try {
    const [docs_json, render_items, root_ids] = await self.pyodide.runPythonAsync(code)
    self.postMessage({
      type: 'render',
      docs_json: docs_json,
      render_items: render_items,
      root_ids: root_ids
    })
  } catch(e) {
    const traceback = `${e}`
    const tblines = traceback.split('\n')
    self.postMessage({
      type: 'status',
      msg: tblines[tblines.length-2]
    });
    throw e
  }
}

self.onmessage = async (event) => {
  const msg = event.data
  if (msg.type === 'rendered') {
    self.pyodide.runPythonAsync(`
    from panel.io.state import state
    from panel.io.pyodide import _link_docs_worker

    _link_docs_worker(state.curdoc, sendPatch, setter='js')
    `)
  } else if (msg.type === 'patch') {
    self.pyodide.globals.set('patch', msg.patch)
    self.pyodide.runPythonAsync(`
    state.curdoc.apply_json_patch(patch.to_py(), setter='js')
    `)
    self.postMessage({type: 'idle'})
  } else if (msg.type === 'location') {
    self.pyodide.globals.set('location', msg.location)
    self.pyodide.runPythonAsync(`
    import json
    from panel.io.state import state
    from panel.util import edit_readonly
    if state.location:
        loc_data = json.loads(location)
        with edit_readonly(state.location):
            state.location.param.update({
                k: v for k, v in loc_data.items() if k in state.location.param
            })
    `)
  }
}

startApplication()

brentgunderson avatar Jan 18 '24 13:01 brentgunderson

Interesting. Is it possible that your browser is caching the app? Could you try in a incognito window?

philippjfr avatar Jan 18 '24 13:01 philippjfr

Success!! Seems to work fine in incognito window.

I'm using Safari Version 17.2.1 (19617.1.17.11.12)

brentgunderson avatar Jan 18 '24 13:01 brentgunderson

Okay, yes, you have to be quite cognizant of the browser caching here, especially when you're using the pyodide-worker convert target because it'll cache the worker script. We should probably add an warning admonition to the docs about this.

philippjfr avatar Jan 18 '24 13:01 philippjfr

Right on. Indeed, I did do this:

panel convert script.py --to pyodide-worker --out pyodide

probably the wrong place for this question, but any suggestion as to what would I need to clear from my browser cache? I had a look but it was not immediately clear to me what to delete, and I was not sure I wanted to delete everything.

I really like panel, very useful package!

brentgunderson avatar Jan 18 '24 13:01 brentgunderson