jupyverse 0.10.5__tar.gz → 0.10.7__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {jupyverse-0.10.5 → jupyverse-0.10.7}/.github/workflows/test.yml +2 -10
- {jupyverse-0.10.5 → jupyverse-0.10.7}/.pre-commit-config.yaml +1 -1
- {jupyverse-0.10.5 → jupyverse-0.10.7}/CHANGELOG.md +10 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/PKG-INFO +7 -8
- jupyverse-0.10.5/tests/utils.py → jupyverse-0.10.7/jupyverse_api/jupyverse_api/asgi_websocket_transport.py +0 -141
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/pyproject.toml +2 -2
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/tests/test_resource_lock.py +5 -5
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth_jupyterhub/fps_auth_jupyterhub/routes.py +2 -2
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth_jupyterhub/pyproject.toml +1 -1
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/file_id/README.md +1 -1
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/file_id/pyproject.toml +1 -1
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernel_web_worker/fps_kernel_web_worker/kernel_web_worker.py +10 -8
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernel_web_worker/pyproject.toml +1 -1
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/noauth/pyproject.toml +3 -3
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/yjs/fps_yjs/routes.py +36 -27
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/yjs/pyproject.toml +1 -1
- jupyverse-0.10.7/plugins/yjs/tests/test_yjs.py +75 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/pyproject.toml +21 -38
- {jupyverse-0.10.5 → jupyverse-0.10.7}/tests/conftest.py +8 -8
- {jupyverse-0.10.5 → jupyverse-0.10.7}/tests/test_kernels.py +1 -1
- jupyverse-0.10.7/tests/utils.py +141 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/.devcontainer/devcontainer.json +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/.devcontainer/requirements.txt +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/.github/workflows/publish-release.yml +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/.gitignore +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/COPYING.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/README.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/binder/environment.yml +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/binder/jupyter_notebook_config.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/binder/postBuild +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/binder/start +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/docs/index.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/docs/install.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/docs/jupyter.svg +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/docs/plugins/auth.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/docs/plugins/contents.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/docs/plugins/frontend.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/docs/plugins/jupyterlab.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/docs/plugins/kernels.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/docs/plugins/lab.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/docs/plugins/login.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/docs/plugins/nbconvert.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/docs/plugins/notebook.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/docs/plugins/resource_usage.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/docs/plugins/terminals.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/docs/plugins/yjs.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/docs/tutorials/jupyterhub_jupyverse_deployment.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/docs/tutorials/standalone_jupyverse_deployment.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/docs/usage/microservices.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/docs/usage/multi_user.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/docs/usage/single_user.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse/py.typed +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/COPYING.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/README.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/jupyverse_api/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/jupyverse_api/app/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/jupyverse_api/auth/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/jupyverse_api/auth/models.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/jupyverse_api/cli.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/jupyverse_api/contents/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/jupyverse_api/contents/models.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/jupyverse_api/exceptions.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/jupyverse_api/file_id/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/jupyverse_api/frontend/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/jupyverse_api/jupyterlab/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/jupyverse_api/kernel/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/jupyverse_api/kernels/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/jupyverse_api/kernels/models.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/jupyverse_api/lab/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/jupyverse_api/login/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/jupyverse_api/main/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/jupyverse_api/nbconvert/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/jupyverse_api/notebook/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/jupyverse_api/py.typed +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/jupyverse_api/resource_usage/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/jupyverse_api/terminals/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/jupyverse_api/terminals/models.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/jupyverse_api/yjs/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/jupyverse_api/jupyverse_api/yjs/models.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/mkdocs.yml +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/notebooks/admin_users.ipynb +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/notebooks/admin_users.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth/COPYING.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth/README.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth/fps_auth/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth/fps_auth/backends.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth/fps_auth/config.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth/fps_auth/db.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth/fps_auth/main.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth/fps_auth/models.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth/fps_auth/py.typed +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth/fps_auth/routes.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth/pyproject.toml +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth_fief/COPYING.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth_fief/README.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth_fief/fps_auth_fief/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth_fief/fps_auth_fief/backend.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth_fief/fps_auth_fief/config.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth_fief/fps_auth_fief/main.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth_fief/fps_auth_fief/py.typed +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth_fief/fps_auth_fief/routes.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth_fief/pyproject.toml +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth_jupyterhub/COPYING.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth_jupyterhub/README.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth_jupyterhub/fps_auth_jupyterhub/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth_jupyterhub/fps_auth_jupyterhub/config.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth_jupyterhub/fps_auth_jupyterhub/db.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth_jupyterhub/fps_auth_jupyterhub/launch.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth_jupyterhub/fps_auth_jupyterhub/main.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth_jupyterhub/fps_auth_jupyterhub/models.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/auth_jupyterhub/fps_auth_jupyterhub/py.typed +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/contents/COPYING.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/contents/README.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/contents/fps_contents/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/contents/fps_contents/main.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/contents/fps_contents/py.typed +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/contents/fps_contents/routes.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/contents/pyproject.toml +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/file_id/COPYING.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/file_id/fps_file_id/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/file_id/fps_file_id/file_id.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/file_id/fps_file_id/main.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/file_id/fps_file_id/py.typed +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/frontend/COPYING.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/frontend/README.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/frontend/fps_frontend/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/frontend/fps_frontend/main.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/frontend/fps_frontend/py.typed +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/frontend/pyproject.toml +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/jupyterlab/COPYING.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/jupyterlab/README.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/jupyterlab/fps_jupyterlab/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/jupyterlab/fps_jupyterlab/index.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/jupyterlab/fps_jupyterlab/main.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/jupyterlab/fps_jupyterlab/py.typed +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/jupyterlab/fps_jupyterlab/routes.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/jupyterlab/pyproject.toml +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernel_subprocess/COPYING.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernel_subprocess/README.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernel_subprocess/fps_kernel_subprocess/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernel_subprocess/fps_kernel_subprocess/connect.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernel_subprocess/fps_kernel_subprocess/kernel_subprocess.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernel_subprocess/fps_kernel_subprocess/main.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernel_subprocess/fps_kernel_subprocess/py.typed +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernel_subprocess/pyproject.toml +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernel_web_worker/COPYING.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernel_web_worker/README.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernel_web_worker/fps_kernel_web_worker/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernel_web_worker/fps_kernel_web_worker/main.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernel_web_worker/fps_kernel_web_worker/py.typed +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernels/COPYING.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernels/README.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernels/fps_kernels/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernels/fps_kernels/kernel_driver/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernels/fps_kernels/kernel_driver/driver.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernels/fps_kernels/kernel_driver/kernelspec.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernels/fps_kernels/kernel_driver/message.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernels/fps_kernels/kernel_driver/paths.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernels/fps_kernels/kernel_server/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernels/fps_kernels/kernel_server/message.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernels/fps_kernels/kernel_server/server.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernels/fps_kernels/main.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernels/fps_kernels/py.typed +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernels/fps_kernels/routes.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernels/pyproject.toml +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/kernels/tests/test_kernel_launcher.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/lab/COPYING.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/lab/README.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/lab/fps_lab/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/lab/fps_lab/main.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/lab/fps_lab/py.typed +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/lab/fps_lab/routes.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/lab/fps_lab/static/favicon.ico +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/lab/pyproject.toml +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/login/COPYING.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/login/README.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/login/fps_login/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/login/fps_login/main.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/login/fps_login/py.typed +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/login/fps_login/routes.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/login/fps_login/static/favicons/favicon-busy-1.ico +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/login/fps_login/static/favicons/favicon-busy-2.ico +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/login/fps_login/static/favicons/favicon-busy-3.ico +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/login/fps_login/static/favicons/favicon-file.ico +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/login/fps_login/static/favicons/favicon-notebook.ico +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/login/fps_login/static/favicons/favicon-terminal.ico +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/login/fps_login/static/favicons/favicon.ico +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/login/fps_login/static/index.html +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/login/fps_login/static/logo/github.svg +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/login/fps_login/static/logo/logo.png +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/login/fps_login/static/style/index.css +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/login/pyproject.toml +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/nbconvert/COPYING.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/nbconvert/README.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/nbconvert/fps_nbconvert/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/nbconvert/fps_nbconvert/main.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/nbconvert/fps_nbconvert/py.typed +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/nbconvert/fps_nbconvert/routes.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/nbconvert/pyproject.toml +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/noauth/COPYING.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/noauth/README.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/noauth/fps_noauth/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/noauth/fps_noauth/backends.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/noauth/fps_noauth/main.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/noauth/fps_noauth/py.typed +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/notebook/COPYING.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/notebook/README.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/notebook/fps_notebook/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/notebook/fps_notebook/main.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/notebook/fps_notebook/py.typed +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/notebook/fps_notebook/routes.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/notebook/pyproject.toml +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/resource_usage/COPYING.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/resource_usage/README.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/resource_usage/fps_resource_usage/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/resource_usage/fps_resource_usage/main.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/resource_usage/fps_resource_usage/py.typed +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/resource_usage/fps_resource_usage/routes.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/resource_usage/pyproject.toml +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/terminals/COPYING.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/terminals/README.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/terminals/fps_terminals/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/terminals/fps_terminals/main.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/terminals/fps_terminals/py.typed +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/terminals/fps_terminals/routes.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/terminals/fps_terminals/server.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/terminals/fps_terminals/win_server.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/terminals/pyproject.toml +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/webdav/COPYING.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/webdav/README.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/webdav/fps_webdav/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/webdav/fps_webdav/config.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/webdav/fps_webdav/main.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/webdav/fps_webdav/py.typed +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/webdav/fps_webdav/routes.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/webdav/pyproject.toml +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/webdav/tests/conftest.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/webdav/tests/test_webdav.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/yjs/COPYING.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/yjs/README.md +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/yjs/fps_yjs/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/yjs/fps_yjs/main.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/yjs/fps_yjs/py.typed +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/yjs/fps_yjs/ywebsocket/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/yjs/fps_yjs/ywebsocket/asgi_server.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/yjs/fps_yjs/ywebsocket/awareness.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/yjs/fps_yjs/ywebsocket/django_channels_consumer.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/yjs/fps_yjs/ywebsocket/websocket.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/yjs/fps_yjs/ywebsocket/websocket_provider.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/yjs/fps_yjs/ywebsocket/websocket_server.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/yjs/fps_yjs/ywebsocket/yroom.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/yjs/fps_yjs/ywebsocket/ystore.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/yjs/fps_yjs/ywebsocket/yutils.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/yjs/fps_yjs/ywidgets/__init__.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/plugins/yjs/fps_yjs/ywidgets/widgets.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/pytest.ini +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/tests/data/notebook0.ipynb +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/tests/data/notebook1.ipynb +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/tests/test_app.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/tests/test_auth.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/tests/test_contents.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/tests/test_execute.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/tests/test_server.py +0 -0
- {jupyverse-0.10.5 → jupyverse-0.10.7}/tests/test_settings.py +0 -0
@@ -29,12 +29,7 @@ jobs:
|
|
29
29
|
python3 -m pip install hatch
|
30
30
|
- name: Check types
|
31
31
|
run: |
|
32
|
-
hatch run dev
|
33
|
-
hatch run dev.jupyterlab-auth:typecheck
|
34
|
-
hatch run dev.jupyterlab-auth_fief:typecheck
|
35
|
-
hatch run dev.notebook-noauth:typecheck
|
36
|
-
hatch run dev.notebook-auth:typecheck
|
37
|
-
hatch run dev.notebook-auth_fief:typecheck
|
32
|
+
hatch run dev:typecheck
|
38
33
|
|
39
34
|
test:
|
40
35
|
name: Tests
|
@@ -59,8 +54,5 @@ jobs:
|
|
59
54
|
python3 -m pip install --upgrade pip
|
60
55
|
python3 -m pip install hatch
|
61
56
|
|
62
|
-
- name: Create jupyterlab-auth dev environment
|
63
|
-
run: hatch env create dev.jupyterlab-auth
|
64
|
-
|
65
57
|
- name: Run tests
|
66
|
-
run: hatch run dev
|
58
|
+
run: hatch run dev:test
|
@@ -1,5 +1,15 @@
|
|
1
1
|
# Version history
|
2
2
|
|
3
|
+
## 0.10.7
|
4
|
+
|
5
|
+
- Lock on collaboration room cleanup.
|
6
|
+
- Move `ASGIWebSocketTransport` to `jupyverse_api`.
|
7
|
+
|
8
|
+
## 0.10.6
|
9
|
+
|
10
|
+
- Bump `fps >=0.5.1`.
|
11
|
+
- Support stopping `fps-kernel-web-worker`.
|
12
|
+
|
3
13
|
## 0.10.5
|
4
14
|
|
5
15
|
- Add `fps-kernel-web-worker` plugin.
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: jupyverse
|
3
|
-
Version: 0.10.
|
3
|
+
Version: 0.10.7
|
4
4
|
Summary: A set of FPS plugins implementing a Jupyter server
|
5
5
|
Project-URL: Homepage, https://jupyter.org
|
6
6
|
Author-email: Jupyter Development Team <jupyter@googlegroups.com>
|
@@ -10,16 +10,16 @@ Keywords: fastapi,jupyter,plugins,server
|
|
10
10
|
Requires-Python: >=3.9
|
11
11
|
Requires-Dist: eval-type-backport; python_version < '3.10'
|
12
12
|
Requires-Dist: fps-contents<0.11.0,>=0.10.0
|
13
|
-
Requires-Dist: fps-file-id<0.3.0,>=0.2.
|
13
|
+
Requires-Dist: fps-file-id<0.3.0,>=0.2.1
|
14
14
|
Requires-Dist: fps-frontend<0.10.0,>=0.9.0
|
15
15
|
Requires-Dist: fps-kernel-subprocess<0.2.0,>=0.1.1
|
16
16
|
Requires-Dist: fps-kernels<0.10.0,>=0.9.1
|
17
17
|
Requires-Dist: fps-lab<0.10.0,>=0.9.0
|
18
18
|
Requires-Dist: fps-nbconvert<0.10.0,>=0.9.0
|
19
19
|
Requires-Dist: fps-terminals<0.10.0,>=0.9.0
|
20
|
-
Requires-Dist: fps-yjs<0.11.0,>=0.10.
|
21
|
-
Requires-Dist: fps[anycorn,click,fastapi]<0.
|
22
|
-
Requires-Dist: jupyverse-api<0.11.0,>=0.10.
|
20
|
+
Requires-Dist: fps-yjs<0.11.0,>=0.10.2
|
21
|
+
Requires-Dist: fps[anycorn,click,fastapi]<0.6.0,>=0.5.1
|
22
|
+
Requires-Dist: jupyverse-api<0.11.0,>=0.10.3
|
23
23
|
Requires-Dist: rich-click<2,>=1.6.1
|
24
24
|
Provides-Extra: auth
|
25
25
|
Requires-Dist: fps-auth<0.10.0,>=0.9.0; extra == 'auth'
|
@@ -27,14 +27,14 @@ Requires-Dist: fps-login<0.10.0,>=0.9.0; extra == 'auth'
|
|
27
27
|
Provides-Extra: auth-fief
|
28
28
|
Requires-Dist: fps-auth-fief<0.10.0,>=0.9.0; extra == 'auth-fief'
|
29
29
|
Provides-Extra: auth-jupyterhub
|
30
|
-
Requires-Dist: fps-auth-jupyterhub<0.10.0,>=0.9.
|
30
|
+
Requires-Dist: fps-auth-jupyterhub<0.10.0,>=0.9.1; extra == 'auth-jupyterhub'
|
31
31
|
Provides-Extra: docs
|
32
32
|
Requires-Dist: mkdocs; extra == 'docs'
|
33
33
|
Requires-Dist: mkdocs-material; extra == 'docs'
|
34
34
|
Provides-Extra: jupyterlab
|
35
35
|
Requires-Dist: fps-jupyterlab<0.10.0,>=0.9.0; extra == 'jupyterlab'
|
36
36
|
Provides-Extra: noauth
|
37
|
-
Requires-Dist: fps-noauth<0.10.0,>=0.9.
|
37
|
+
Requires-Dist: fps-noauth<0.10.0,>=0.9.1; extra == 'noauth'
|
38
38
|
Provides-Extra: notebook
|
39
39
|
Requires-Dist: fps-notebook<0.10.0,>=0.9.0; extra == 'notebook'
|
40
40
|
Provides-Extra: test
|
@@ -44,7 +44,6 @@ Requires-Dist: ipykernel; extra == 'test'
|
|
44
44
|
Requires-Dist: mypy; extra == 'test'
|
45
45
|
Requires-Dist: pytest; extra == 'test'
|
46
46
|
Requires-Dist: pytest-env; extra == 'test'
|
47
|
-
Requires-Dist: pytest-rerunfailures; extra == 'test'
|
48
47
|
Requires-Dist: pytest-timeout; extra == 'test'
|
49
48
|
Requires-Dist: requests; extra == 'test'
|
50
49
|
Requires-Dist: ruff>=0.1.2; extra == 'test'
|
@@ -2,155 +2,14 @@ import contextlib
|
|
2
2
|
import queue
|
3
3
|
import typing
|
4
4
|
from types import TracebackType
|
5
|
-
from typing import Optional
|
6
|
-
from uuid import uuid4
|
7
5
|
|
8
6
|
import anyio
|
9
7
|
import wsproto
|
10
|
-
from anyio import Lock
|
11
8
|
from httpcore import AsyncNetworkStream
|
12
9
|
from httpx import ASGITransport, AsyncByteStream, Request, Response
|
13
10
|
from httpx_ws._exceptions import WebSocketDisconnect
|
14
11
|
from wsproto.frame_protocol import CloseReason
|
15
12
|
|
16
|
-
|
17
|
-
async def authenticate_client(http, port, permissions={}):
|
18
|
-
# create a new user
|
19
|
-
username = uuid4().hex
|
20
|
-
# if logged in, log out
|
21
|
-
first_time = True
|
22
|
-
while True:
|
23
|
-
response = await http.get(f"http://127.0.0.1:{port}/api/me")
|
24
|
-
if response.status_code == 403:
|
25
|
-
break
|
26
|
-
assert first_time
|
27
|
-
response = await http.post(f"http://127.0.0.1:{port}/auth/logout")
|
28
|
-
assert response.status_code == 200
|
29
|
-
first_time = False
|
30
|
-
|
31
|
-
# register user
|
32
|
-
register_body = {
|
33
|
-
"email": f"{username}@example.com",
|
34
|
-
"password": username,
|
35
|
-
"username": username,
|
36
|
-
"permissions": permissions,
|
37
|
-
}
|
38
|
-
response = await http.post(f"http://127.0.0.1:{port}/auth/register", json=register_body)
|
39
|
-
# check that we cannot register if not logged in
|
40
|
-
assert response.status_code == 403
|
41
|
-
# log in as admin
|
42
|
-
login_body = {"username": "admin@jupyter.com", "password": "jupyverse"}
|
43
|
-
response = await http.post(f"http://127.0.0.1:{port}/auth/login", data=login_body)
|
44
|
-
assert response.status_code == 204
|
45
|
-
# register user
|
46
|
-
response = await http.post(f"http://127.0.0.1:{port}/auth/register", json=register_body)
|
47
|
-
assert response.status_code == 201
|
48
|
-
|
49
|
-
# log out
|
50
|
-
response = await http.post(f"http://127.0.0.1:{port}/auth/logout")
|
51
|
-
assert response.status_code == 204
|
52
|
-
# check that we can't get our identity, since we're not logged in
|
53
|
-
response = await http.get(f"http://127.0.0.1:{port}/api/me")
|
54
|
-
assert response.status_code == 403
|
55
|
-
|
56
|
-
# log in with registered user
|
57
|
-
login_body = {"username": f"{username}@example.com", "password": username}
|
58
|
-
response = await http.post(f"http://127.0.0.1:{port}/auth/login", data=login_body)
|
59
|
-
assert response.status_code == 204
|
60
|
-
# we should now have a cookie
|
61
|
-
assert "fastapiusersauth" in http.cookies
|
62
|
-
# check our identity, since we're logged in
|
63
|
-
response = await http.get(
|
64
|
-
f"http://127.0.0.1:{port}/api/me", params={"permissions": permissions}
|
65
|
-
)
|
66
|
-
assert response.status_code == 200
|
67
|
-
me = response.json()
|
68
|
-
assert me["identity"]["username"] == username
|
69
|
-
# check our permissions
|
70
|
-
assert me["permissions"] == permissions
|
71
|
-
|
72
|
-
|
73
|
-
def create_content(
|
74
|
-
content: Optional[list],
|
75
|
-
type: str,
|
76
|
-
size: Optional[int],
|
77
|
-
mimetype: Optional[str],
|
78
|
-
name: str,
|
79
|
-
path: str,
|
80
|
-
format: Optional[str],
|
81
|
-
) -> dict:
|
82
|
-
return {
|
83
|
-
"content": content,
|
84
|
-
"created": None,
|
85
|
-
"format": format,
|
86
|
-
"last_modified": None,
|
87
|
-
"mimetype": mimetype,
|
88
|
-
"name": name,
|
89
|
-
"path": path,
|
90
|
-
"size": size,
|
91
|
-
"type": type,
|
92
|
-
"writable": True,
|
93
|
-
}
|
94
|
-
|
95
|
-
|
96
|
-
def clear_content_values(content: dict, keys: list[str] = []):
|
97
|
-
for k in content:
|
98
|
-
if k in keys:
|
99
|
-
content[k] = None
|
100
|
-
if k == "content" and isinstance(content[k], list):
|
101
|
-
for c in content[k]:
|
102
|
-
clear_content_values(c, keys)
|
103
|
-
return content
|
104
|
-
|
105
|
-
|
106
|
-
def sort_content_by_name(content: dict):
|
107
|
-
for k in content:
|
108
|
-
if k == "content" and isinstance(content[k], list):
|
109
|
-
# FIXME: this sorting algorithm is terrible!
|
110
|
-
names = [c["name"] for c in content[k]]
|
111
|
-
names.sort()
|
112
|
-
new_content = []
|
113
|
-
for name in names:
|
114
|
-
for i, c in enumerate(content[k]):
|
115
|
-
if c["name"] == name:
|
116
|
-
break
|
117
|
-
content[k].pop(i)
|
118
|
-
new_content.append(c)
|
119
|
-
content[k] = new_content
|
120
|
-
for c in content[k]:
|
121
|
-
sort_content_by_name(c)
|
122
|
-
return content
|
123
|
-
|
124
|
-
|
125
|
-
class Websocket:
|
126
|
-
def __init__(self, websocket, path: str):
|
127
|
-
self._websocket = websocket
|
128
|
-
self._path = path
|
129
|
-
self._send_lock = Lock()
|
130
|
-
|
131
|
-
@property
|
132
|
-
def path(self) -> str:
|
133
|
-
return self._path
|
134
|
-
|
135
|
-
def __aiter__(self):
|
136
|
-
return self
|
137
|
-
|
138
|
-
async def __anext__(self) -> bytes:
|
139
|
-
try:
|
140
|
-
message = await self.recv()
|
141
|
-
except Exception:
|
142
|
-
raise StopAsyncIteration()
|
143
|
-
return message
|
144
|
-
|
145
|
-
async def send(self, message: bytes):
|
146
|
-
async with self._send_lock:
|
147
|
-
await self._websocket.send_bytes(message)
|
148
|
-
|
149
|
-
async def recv(self) -> bytes:
|
150
|
-
b = await self._websocket.receive_bytes()
|
151
|
-
return bytes(b)
|
152
|
-
|
153
|
-
|
154
13
|
Scope = dict[str, typing.Any]
|
155
14
|
Message = dict[str, typing.Any]
|
156
15
|
Receive = typing.Callable[[], typing.Awaitable[Message]]
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "jupyverse_api"
|
7
|
-
version = "0.10.
|
7
|
+
version = "0.10.3"
|
8
8
|
description = "The public API for Jupyverse"
|
9
9
|
readme = "README.md"
|
10
10
|
requires-python = ">=3.9"
|
@@ -29,7 +29,7 @@ dependencies = [
|
|
29
29
|
"importlib_metadata >=3.6; python_version<'3.10'",
|
30
30
|
"pydantic >=2,<3",
|
31
31
|
"fastapi >=0.95.0,<1",
|
32
|
-
"fps >=0.
|
32
|
+
"fps >=0.5.1,<0.6.0",
|
33
33
|
"anyio >=3.6.2,<5",
|
34
34
|
]
|
35
35
|
|
@@ -10,7 +10,7 @@ async def do_op(operation, resource_lock, operations):
|
|
10
10
|
op, path = operation
|
11
11
|
async with resource_lock(path):
|
12
12
|
operations.append(operation + ["start"])
|
13
|
-
await sleep(0.
|
13
|
+
await sleep(0.5)
|
14
14
|
operations.append(operation + ["done"])
|
15
15
|
|
16
16
|
|
@@ -22,7 +22,7 @@ async def test_resource_lock():
|
|
22
22
|
operations = []
|
23
23
|
async with create_task_group() as tg:
|
24
24
|
tg.start_soon(do_op, [0, idx], resource_lock, operations)
|
25
|
-
await sleep(0.
|
25
|
+
await sleep(0.05)
|
26
26
|
tg.start_soon(do_op, [1, idx], resource_lock, operations)
|
27
27
|
|
28
28
|
assert operations == [
|
@@ -38,11 +38,11 @@ async def test_resource_lock():
|
|
38
38
|
operations = []
|
39
39
|
async with create_task_group() as tg:
|
40
40
|
tg.start_soon(do_op, [0, idx0], resource_lock, operations)
|
41
|
-
await sleep(0.
|
41
|
+
await sleep(0.05)
|
42
42
|
tg.start_soon(do_op, [1, idx1], resource_lock, operations)
|
43
|
-
await sleep(0.
|
43
|
+
await sleep(0.05)
|
44
44
|
tg.start_soon(do_op, [2, idx0], resource_lock, operations)
|
45
|
-
await sleep(0.
|
45
|
+
await sleep(0.05)
|
46
46
|
tg.start_soon(do_op, [3, idx1], resource_lock, operations)
|
47
47
|
|
48
48
|
assert operations == [
|
@@ -12,8 +12,8 @@ from anyio.abc import TaskStatus
|
|
12
12
|
from fastapi import APIRouter, Cookie, Depends, HTTPException, Request, WebSocket, status
|
13
13
|
from fastapi.responses import RedirectResponse
|
14
14
|
from httpx import AsyncClient
|
15
|
-
from jupyterhub.services.auth import HubOAuth
|
16
|
-
from jupyterhub.utils import isoformat
|
15
|
+
from jupyterhub.services.auth import HubOAuth # type: ignore[import-untyped]
|
16
|
+
from jupyterhub.utils import isoformat # type: ignore[import-untyped]
|
17
17
|
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
|
18
18
|
from sqlalchemy.future import select
|
19
19
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "fps_auth_jupyterhub"
|
7
|
-
version = "0.9.
|
7
|
+
version = "0.9.1"
|
8
8
|
description = "An FPS plugin for the authentication API, using JupyterHbu"
|
9
9
|
keywords = ["jupyter", "server", "fastapi", "plugins"]
|
10
10
|
requires-python = ">=3.9"
|
@@ -33,25 +33,26 @@ class KernelWebWorker(Kernel):
|
|
33
33
|
kernel_ready = Event()
|
34
34
|
|
35
35
|
def callback(msg):
|
36
|
-
|
36
|
+
msg_type = msg["type"]
|
37
|
+
if msg_type == "started":
|
37
38
|
kernel_ready.set()
|
38
39
|
else:
|
39
40
|
msg = [bytes(pyjs.to_py(m)) for m in msg["msg"]]
|
40
|
-
if
|
41
|
+
if msg_type == "shell":
|
41
42
|
self.task_group.start_soon(self._from_shell_send_stream.send, msg)
|
42
|
-
elif
|
43
|
+
elif msg_type == "control":
|
43
44
|
self.task_group.start_soon(self._from_control_send_stream.send, msg)
|
44
|
-
elif
|
45
|
+
elif msg_type == "stdin":
|
45
46
|
self.task_group.start_soon(self._from_stdin_send_stream.send, msg)
|
46
|
-
elif
|
47
|
+
elif msg_type == "iopub":
|
47
48
|
self.task_group.start_soon(self._from_iopub_send_stream.send, msg)
|
48
49
|
|
49
|
-
js_callable, self.js_py_object = pyjs.create_callable(callback)
|
50
|
-
higher_order_function = pyjs.js.Function(
|
50
|
+
self.js_callable, self.js_py_object = pyjs.create_callable(callback)
|
51
|
+
self.higher_order_function = pyjs.js.Function(
|
51
52
|
"callback", "action", "kernel_id",
|
52
53
|
"kernel_web_worker(action, kernel_id, 0, callback);"
|
53
54
|
)
|
54
|
-
higher_order_function(js_callable, "start", self.kernel_id)
|
55
|
+
self.higher_order_function(self.js_callable, "start", self.kernel_id)
|
55
56
|
await kernel_ready.wait()
|
56
57
|
|
57
58
|
self.task_group.start_soon(self.forward_messages_to_shell)
|
@@ -62,6 +63,7 @@ class KernelWebWorker(Kernel):
|
|
62
63
|
self.started.set()
|
63
64
|
|
64
65
|
async def stop(self) -> None:
|
66
|
+
self.higher_order_function(self.js_callable, "stop", self.kernel_id)
|
65
67
|
self.js_py_object.delete()
|
66
68
|
self.task_group.cancel_scope.cancel()
|
67
69
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "fps_kernel_web_worker"
|
7
|
-
version = "0.1.
|
7
|
+
version = "0.1.3"
|
8
8
|
description = "An FPS plugin for the kernel web worker API"
|
9
9
|
keywords = ["jupyter", "server", "fastapi", "plugins"]
|
10
10
|
requires-python = ">=3.9"
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "fps_noauth"
|
7
|
-
version = "0.9.
|
7
|
+
version = "0.9.1"
|
8
8
|
description = "An FPS plugin for an unprotected API"
|
9
9
|
keywords = ["jupyter", "server", "fastapi", "plugins"]
|
10
10
|
requires-python = ">=3.9"
|
@@ -27,8 +27,8 @@ text = "BSD 3-Clause License"
|
|
27
27
|
Homepage = "https://jupyter.org"
|
28
28
|
|
29
29
|
[project.entry-points]
|
30
|
-
"fps.modules" = {
|
31
|
-
"jupyverse.modules" = {
|
30
|
+
"fps.modules" = {noauth = "fps_noauth.main:NoAuthModule"}
|
31
|
+
"jupyverse.modules" = {noauth = "fps_noauth.main:NoAuthModule"}
|
32
32
|
|
33
33
|
[tool.check-manifest]
|
34
34
|
ignore = [ ".*",]
|
@@ -268,15 +268,22 @@ class RoomManager:
|
|
268
268
|
|
269
269
|
await self.websocket_server.serve(websocket, self.lifespan.shutdown_request)
|
270
270
|
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
self.
|
278
|
-
|
279
|
-
|
271
|
+
# Obtain lock for cleanup to prevent race conditions when multiple clients disconnect
|
272
|
+
async with self.room_lock(websocket.path):
|
273
|
+
if websocket in self.room_write_permissions.get(websocket.path, set()):
|
274
|
+
self.room_write_permissions[websocket.path].remove(websocket)
|
275
|
+
|
276
|
+
if (
|
277
|
+
not self.lifespan.shutdown_request.is_set()
|
278
|
+
and is_stored_document
|
279
|
+
and not room.clients
|
280
|
+
and room not in self.cleaners
|
281
|
+
):
|
282
|
+
# no client in this room after we disconnect
|
283
|
+
self.cleaners[room] = create_task(
|
284
|
+
self.maybe_clean_room(room, websocket.path),
|
285
|
+
self.task_group,
|
286
|
+
)
|
280
287
|
|
281
288
|
async def filter_message(self, message: bytes, websocket: Websocket) -> bool:
|
282
289
|
"""
|
@@ -405,25 +412,27 @@ class RoomManager:
|
|
405
412
|
file_id = ws_path.split(":", 2)[2]
|
406
413
|
# keep the document for a while in case someone reconnects
|
407
414
|
await sleep(self.config.document_cleanup_delay)
|
408
|
-
document = self.documents[ws_path]
|
409
|
-
document.unobserve()
|
410
|
-
del self.documents[ws_path]
|
411
|
-
documents = [v for k, v in self.documents.items() if k.split(":", 2)[2] == file_id]
|
412
|
-
if not documents:
|
413
|
-
self.watchers[file_id].cancel(raise_exception=False)
|
414
|
-
await self.watchers[file_id].wait()
|
415
|
-
if file_id in self.watchers:
|
416
|
-
del self.watchers[file_id]
|
417
|
-
room_name = self.websocket_server.get_room_name(room)
|
418
|
-
self.websocket_server.delete_room(room=room)
|
419
|
-
|
420
|
-
if ws_path in self.room_write_permissions:
|
421
|
-
del self.room_write_permissions[ws_path]
|
422
415
|
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
del self.
|
416
|
+
async with self.room_lock(ws_path):
|
417
|
+
document = self.documents[ws_path]
|
418
|
+
document.unobserve()
|
419
|
+
del self.documents[ws_path]
|
420
|
+
documents = [v for k, v in self.documents.items() if k.split(":", 2)[2] == file_id]
|
421
|
+
if not documents:
|
422
|
+
self.watchers[file_id].cancel(raise_exception=False)
|
423
|
+
await self.watchers[file_id].wait()
|
424
|
+
if file_id in self.watchers:
|
425
|
+
del self.watchers[file_id]
|
426
|
+
room_name = self.websocket_server.get_room_name(room)
|
427
|
+
self.websocket_server.delete_room(room=room)
|
428
|
+
|
429
|
+
if ws_path in self.room_write_permissions:
|
430
|
+
del self.room_write_permissions[ws_path]
|
431
|
+
|
432
|
+
file_path = await self.get_file_path(file_id, document)
|
433
|
+
logger.info("Closing collaboration room", room_id=room_name, file_path=file_path)
|
434
|
+
if room in self.cleaners:
|
435
|
+
del self.cleaners[room]
|
427
436
|
|
428
437
|
|
429
438
|
class JupyterWebsocketServer(WebsocketServer):
|
@@ -0,0 +1,75 @@
|
|
1
|
+
import pytest
|
2
|
+
from anyio import create_task_group
|
3
|
+
from fps import get_root_module
|
4
|
+
from httpx import AsyncClient
|
5
|
+
from httpx_ws import aconnect_ws
|
6
|
+
from structlog.testing import capture_logs
|
7
|
+
|
8
|
+
from jupyverse_api.asgi_websocket_transport import ASGIWebSocketTransport
|
9
|
+
from jupyverse_api.yjs.models import CreateDocumentSession
|
10
|
+
|
11
|
+
|
12
|
+
@pytest.mark.anyio
|
13
|
+
async def test_concurrent_disconnect(tmp_path):
|
14
|
+
config = {
|
15
|
+
"jupyverse": {
|
16
|
+
"type": "jupyverse",
|
17
|
+
"config": {
|
18
|
+
"start_server": False,
|
19
|
+
},
|
20
|
+
"modules": {
|
21
|
+
"app": {
|
22
|
+
"type": "app",
|
23
|
+
},
|
24
|
+
"yjs": {
|
25
|
+
"type": "yjs",
|
26
|
+
"config": {
|
27
|
+
"document_cleanup_delay": 0,
|
28
|
+
},
|
29
|
+
},
|
30
|
+
"auth": {
|
31
|
+
"type": "noauth",
|
32
|
+
},
|
33
|
+
"contents": {
|
34
|
+
"type": "contents",
|
35
|
+
},
|
36
|
+
"file_id": {
|
37
|
+
"type": "file_id",
|
38
|
+
},
|
39
|
+
"frontend": {
|
40
|
+
"type": "frontend",
|
41
|
+
},
|
42
|
+
},
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
with capture_logs() as cap_logs:
|
47
|
+
async with get_root_module(config) as root_module:
|
48
|
+
app = root_module.app
|
49
|
+
transport = ASGIWebSocketTransport(app=app)
|
50
|
+
async with AsyncClient(transport=transport, base_url="http://testserver") as client:
|
51
|
+
data = CreateDocumentSession(format="text", type="file")
|
52
|
+
p = tmp_path / "hello.txt"
|
53
|
+
p.write_text("hello")
|
54
|
+
response = await client.put(
|
55
|
+
f"http://testserver/api/collaboration/session/{p}",
|
56
|
+
json=dict(data),
|
57
|
+
)
|
58
|
+
data = response.json()
|
59
|
+
file_id = data["fileId"]
|
60
|
+
|
61
|
+
async def connect_ws():
|
62
|
+
async with aconnect_ws(
|
63
|
+
f"http://testserver/api/collaboration/room/text:file:{file_id}",
|
64
|
+
client,
|
65
|
+
):
|
66
|
+
pass
|
67
|
+
|
68
|
+
async with create_task_group() as tg:
|
69
|
+
tg.start_soon(connect_ws)
|
70
|
+
tg.start_soon(connect_ws)
|
71
|
+
|
72
|
+
assert {
|
73
|
+
"event": "Application failed",
|
74
|
+
"log_level": "critical",
|
75
|
+
} not in cap_logs
|