widgetti / solara

A Pure Python, React-style Framework for Scaling Your Jupyter and Web Apps
https://solara.dev
MIT License
1.9k stars 140 forks source link

PermissionError for nbextensions on startup with v1.29+ #611

Open brian-c-ogorman opened 6 months ago

brian-c-ogorman commented 6 months ago

I'm getting the following error when attempting to run Solara with version 1.29+:

PermissionError: [Errno 13] Permission denied: '/usr/local/share/jupyter/nbextensions/jupyter-vuetify/extension.js'

Everything works fine on 1.28.0 though. I started a thread in the discord questions channel, but it seems like an issue might be more appropriate.

This is the logging output with the stacktrace:

2024-04-18 16:04:17,505 [INFO ] [79501] solara.server.app: app-status check: 81d2c0e7-a41d-4c15-a4b5-8d12eca8cf73 app not started
2024-04-18 16:04:17,527 [INFO ] [79501] solara.server.app: Shut down virtual kernel: 81d2c0e7-a41d-4c15-a4b5-8d12eca8cf73
2024-04-18 16:04:17,574 [INFO ] [79501] solara.server.app: Disconnect page df0beeeb-043e-4b84-b676-2e470826b8ba for kernel 81d2c0e7-a41d-4c15-a4b5-8d12eca8cf73
2024-04-18 16:04:17,576 [INFO ] [79501] solara.server.app: Scheduling kernel cull, will wait for max 86400.0 before shutting down the virtual kernel 81d2c0e7-a41d-4c15-a4b5-8d12eca8cf73
2024-04-18 16:04:20,734 [ERROR] [79501] uvicorn.error: Exception in ASGI application
Traceback (most recent call last):
  File "/.../venv/solara-1-29/lib/python3.10/site-packages/uvicorn/protocols/http/h11_impl.py", line 407, in run_asgi
    result = await app(  # type: ignore[func-returns-value]
  File "/.../venv/solara-1-29/lib/python3.10/site-packages/uvicorn/middleware/proxy_headers.py", line 69, in __call__
    return await self.app(scope, receive, send)
  File "/.../venv/solara-1-29/lib/python3.10/site-packages/starlette/applications.py", line 123, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/.../venv/solara-1-29/lib/python3.10/site-packages/starlette/middleware/errors.py", line 186, in __call__
    raise exc
  File "/.../venv/solara-1-29/lib/python3.10/site-packages/starlette/middleware/errors.py", line 164, in __call__
    await self.app(scope, receive, _send)
  File "/.../venv/solara-1-29/lib/python3.10/site-packages/starlette/middleware/gzip.py", line 24, in __call__
    await responder(scope, receive, send)
  File "/.../venv/solara-1-29/lib/python3.10/site-packages/starlette/middleware/gzip.py", line 44, in __call__
    await self.app(scope, receive, self.send_with_gzip)
  File "/.../venv/solara-1-29/lib/python3.10/site-packages/starlette/middleware/exceptions.py", line 65, in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
  File "/.../venv/solara-1-29/lib/python3.10/site-packages/starlette/_exception_handler.py", line 64, in wrapped_app
    raise exc
  File "/.../venv/solara-1-29/lib/python3.10/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
    await app(scope, receive, sender)
  File "/.../venv/solara-1-29/lib/python3.10/site-packages/starlette/routing.py", line 756, in __call__
    await self.middleware_stack(scope, receive, send)
  File "/.../venv/solara-1-29/lib/python3.10/site-packages/starlette/routing.py", line 776, in app
    await route.handle(scope, receive, send)
  File "/.../venv/solara-1-29/lib/python3.10/site-packages/starlette/routing.py", line 297, in handle
    await self.app(scope, receive, send)
  File "/.../venv/solara-1-29/lib/python3.10/site-packages/starlette/routing.py", line 77, in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
  File "/.../venv/solara-1-29/lib/python3.10/site-packages/starlette/_exception_handler.py", line 64, in wrapped_app
    raise exc
  File "/.../venv/solara-1-29/lib/python3.10/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
    await app(scope, receive, sender)
  File "/.../venv/solara-1-29/lib/python3.10/site-packages/starlette/routing.py", line 72, in app
    response = await func(request)
  File "/.../venv/solara-1-29/lib/python3.10/site-packages/solara/server/starlette.py", line 318, in root
    content = server.read_root(request_path, root_path)
  File "/.../venv/solara-1-29/lib/python3.10/site-packages/solara/server/server.py", line 266, in read_root
    nbextensions, nbextensions_hashes = get_nbextensions()
  File "/.../venv/solara-1-29/lib/python3.10/site-packages/solara/cache.py", line 100, in __call__
    value = self.function(*args, **kwargs)
  File "/.../venv/solara-1-29/lib/python3.10/site-packages/solara/server/server.py", line 432, in get_nbextensions
    nbextensions_hashes = {name: hash_extension(name) for name in nbextensions}
  File "/.../venv/solara-1-29/lib/python3.10/site-packages/solara/server/server.py", line 432, in <dictcomp>
    nbextensions_hashes = {name: hash_extension(name) for name in nbextensions}
  File "/.../venv/solara-1-29/lib/python3.10/site-packages/solara/server/server.py", line 424, in hash_extension
    if (directory / (name + ".js")).exists():
  File "~/miniconda3/envs/py310/lib/python3.10/pathlib.py", line 1290, in exists
    self.stat()
  File "~/miniconda3/envs/py310/lib/python3.10/pathlib.py", line 1097, in stat
    return self._accessor.stat(self, follow_symlinks=follow_symlinks)
PermissionError: [Errno 13] Permission denied: '/usr/local/share/jupyter/nbextensions/jupyter-vuetify/extension.js'

It looks like it is related to this change, though whether that change is the cause or simply exposed the issue, I do not know.

brian-c-ogorman commented 6 months ago

I have learned a bit more on this. I think my issue comes from this existence check here.

It seems that paths returned by:

from jupyter_core.paths import jupyter_path

jupyter_path("nbextensions")

do not always exist (which is why the function is calling exists()). In my case, the file that was being checked (/usr/local/share/jupyter/nbextensions/jupyter-vuetify/extension.js) did not exist.

However, pathlib.Path.exists() will raise a PermissionError if it does not have permission to read from the location/file. And in my case, my permissions to /usr/local/share were set to:

drwx------

It worked when I updated the directory permissions to:

drwxr-xr-x

While this does solve my issue, I think the code could be more robust here. If the user doesn't have permission to a folder or file, shouldn't that be considered the same as "does not exist"? That is, maybe the call to exists() should be wrapped in a try/except PermissionError, and then ignore paths/files that the user does not have permission to?

maartenbreddels commented 6 months ago

Hi Brian,

thank you for the detailed explanation. This should be a relative easy fix (although difficult to test in CI).

Regards,

Maarten

maartenbreddels commented 6 months ago

Do you feel comfortable opening a PR for this? https://solara.dev/documentation/advanced/development/setup should guide you though it.

brian-c-ogorman commented 6 months ago

Hi @maartenbreddels, sure, I'd like to give it a try 🙂. I might need some guidance on the testing bit.

I just tried setting up a development environment, but I ran into some issues creating a virtual environment from requirements-dev.txt. The development setup instructions don't specify which version of Python to use, so I tried both Python 3.9 and 3.10, but I had issues with both.

With Python 3.9:

ERROR: Ignored the following versions that require a different python version: 0.10.6 Requires-Python >=2.7, <3.6; 0.10.7 Requires-Python >=2.7, <3.6; 8.19.0 Requires-Python >=3.10; 8.20.0 Requires-Python >=3.10; 8.21.0 Requires-Python >=3.10; 8.22.0 Requires-Python >=3.10; 8.22.1 Requires-Python >=3.10; 8.22.2 Requires-Python >=3.10; 8.23.0 Requires-Python >=3.10; 8.24.0 Requires-Python >=3.10
ERROR: Could not find a version that satisfies the requirement pytest-ipywidgets==1.32.1; extra == "pytest" (from solara[pytest]) (from versions: 1.31.0)
ERROR: No matching distribution found for pytest-ipywidgets==1.32.1; extra == "pytest"

With Python 3.10:

ERROR: Ignored the following versions that require a different python version: 0.10.6 Requires-Python >=2.7, <3.6; 0.10.7 Requires-Python >=2.7, <3.6
ERROR: Could not find a version that satisfies the requirement pytest-ipywidgets==1.32.1; extra == "pytest" (from solara[pytest]) (from versions: 1.31.0)
ERROR: No matching distribution found for pytest-ipywidgets==1.32.1; extra == "pytest"

I'm not sure if it is okay to ignore these?

iisakkirotko commented 6 months ago

Hey @brian-c-ogorman!

It looks like pytest-ipywidgets hasn't been released for the last couple releases we've done (it should always release when we release solara). We'll get the releases out ASAP.

iisakkirotko commented 6 months ago

@brian-c-ogorman the dev install should work now!