├── .github └── workflows │ ├── publish.yml │ └── test.yml ├── .gitignore ├── README.md ├── datasette_jupyterlite └── __init__.py ├── setup.py └── tests └── test_jupyterlite.py /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Publish Python Package 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | test: 9 | runs-on: ubuntu-latest 10 | strategy: 11 | matrix: 12 | python-version: ["3.7", "3.8", "3.9", "3.10"] 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Set up Python ${{ matrix.python-version }} 16 | uses: actions/setup-python@v2 17 | with: 18 | python-version: ${{ matrix.python-version }} 19 | - uses: actions/cache@v2 20 | name: Configure pip caching 21 | with: 22 | path: ~/.cache/pip 23 | key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }} 24 | restore-keys: | 25 | ${{ runner.os }}-pip- 26 | - name: Install dependencies 27 | run: | 28 | pip install -e '.[test]' 29 | - name: Run tests 30 | run: | 31 | pytest 32 | deploy: 33 | runs-on: ubuntu-latest 34 | needs: [test] 35 | steps: 36 | - uses: actions/checkout@v2 37 | - name: Set up Python 38 | uses: actions/setup-python@v2 39 | with: 40 | python-version: '3.9' 41 | - uses: actions/cache@v2 42 | name: Configure pip caching 43 | with: 44 | path: ~/.cache/pip 45 | key: ${{ runner.os }}-publish-pip-${{ hashFiles('**/setup.py') }} 46 | restore-keys: | 47 | ${{ runner.os }}-publish-pip- 48 | - name: Install dependencies 49 | run: | 50 | pip install setuptools wheel twine build 51 | - name: Publish 52 | env: 53 | TWINE_USERNAME: __token__ 54 | TWINE_PASSWORD: ${{ secrets.PYPI_TOKEN }} 55 | run: | 56 | python -m build 57 | twine upload dist/* 58 | 59 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | runs-on: ubuntu-latest 8 | strategy: 9 | matrix: 10 | python-version: ["3.7", "3.8", "3.9", "3.10"] 11 | steps: 12 | - uses: actions/checkout@v2 13 | - name: Set up Python ${{ matrix.python-version }} 14 | uses: actions/setup-python@v2 15 | with: 16 | python-version: ${{ matrix.python-version }} 17 | - uses: actions/cache@v2 18 | name: Configure pip caching 19 | with: 20 | path: ~/.cache/pip 21 | key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }} 22 | restore-keys: | 23 | ${{ runner.os }}-pip- 24 | - name: Install dependencies 25 | run: | 26 | pip install -e '.[test]' 27 | - name: Run tests 28 | run: | 29 | pytest 30 | 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .venv 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | venv 6 | .eggs 7 | .pytest_cache 8 | *.egg-info 9 | .DS_Store 10 | .vscode 11 | dist 12 | build 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # datasette-jupyterlite 2 | 3 | [![PyPI](https://img.shields.io/pypi/v/datasette-jupyterlite.svg)](https://pypi.org/project/datasette-jupyterlite/) 4 | [![Changelog](https://img.shields.io/github/v/release/simonw/datasette-jupyterlite?include_prereleases&label=changelog)](https://github.com/simonw/datasette-jupyterlite/releases) 5 | [![Tests](https://github.com/simonw/datasette-jupyterlite/workflows/Test/badge.svg)](https://github.com/simonw/datasette-jupyterlite/actions?query=workflow%3ATest) 6 | [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/simonw/datasette-jupyterlite/blob/main/LICENSE) 7 | 8 | [JupyterLite](https://jupyterlite.readthedocs.io/en/latest/) as a Datasette plugin 9 | 10 | ## Installation 11 | 12 | Install this plugin in the same environment as Datasette. 13 | 14 | $ datasette install datasette-jupyterlite 15 | 16 | ## Demo 17 | 18 | You can try out a demo of the plugin here: https://latest-with-plugins.datasette.io/jupyterlite/ 19 | 20 | Run this example code in a Pyolite notebook to pull all of the data from the [github/stars](https://latest-with-plugins.datasette.io/github/stars) table into a Pandas DataFrame: 21 | 22 | ```python 23 | import pandas, pyodide 24 | 25 | df = pandas.read_csv(pyodide.open_url( 26 | "https://latest-with-plugins.datasette.io/github/stars.csv?_labels=on&_stream=on&_size=max") 27 | ) 28 | ``` 29 | 30 | ## Usage 31 | 32 | Once installed, visit `/jupyterlite/` to access JupyterLite served from your Datasette instance. 33 | 34 | ## Development 35 | 36 | To set up this plugin locally, first checkout the code. Then create a new virtual environment: 37 | 38 | cd datasette-jupyterlite 39 | python3 -mvenv venv 40 | source venv/bin/activate 41 | 42 | Or if you are using `pipenv`: 43 | 44 | pipenv shell 45 | 46 | Now install the dependencies and test dependencies: 47 | 48 | pip install -e '.[test]' 49 | 50 | To run the tests: 51 | 52 | pytest 53 | -------------------------------------------------------------------------------- /datasette_jupyterlite/__init__.py: -------------------------------------------------------------------------------- 1 | from datasette import hookimpl 2 | from datasette.utils.asgi import Response, NotFound 3 | import mimetypes 4 | from importlib_resources import files 5 | import tarfile 6 | 7 | 8 | tarpath = list(files("jupyterlite").glob("*.tgz"))[0] 9 | tf = tarfile.open(tarpath) 10 | 11 | 12 | async def serve_juptyerlite(request): 13 | path = request.url_vars["path"] 14 | if not path: 15 | path = "index.html" 16 | path = "package/" + path 17 | try: 18 | member = tf.getmember(path) 19 | except KeyError: 20 | raise NotFound("Path not found: {}".format(path)) 21 | # Set content-type based on extension 22 | content_type = mimetypes.guess_type(path)[0] 23 | if content_type is None: 24 | content_type = "application/octet-stream" 25 | return Response(tf.extractfile(member).read(), content_type=content_type) 26 | 27 | 28 | @hookimpl 29 | def register_routes(): 30 | return [(r"^/jupyterlite/(?P.*)$", serve_juptyerlite)] 31 | 32 | 33 | @hookimpl 34 | def menu_links(datasette): 35 | return [ 36 | {"href": datasette.urls.path("/jupyterlite/"), "label": "JupyterLite"}, 37 | ] 38 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | import os 3 | 4 | VERSION = "0.1a1" 5 | 6 | 7 | def get_long_description(): 8 | with open( 9 | os.path.join(os.path.dirname(os.path.abspath(__file__)), "README.md"), 10 | encoding="utf8", 11 | ) as fp: 12 | return fp.read() 13 | 14 | 15 | setup( 16 | name="datasette-jupyterlite", 17 | description="JupyterLite as a Datasette plugin", 18 | long_description=get_long_description(), 19 | long_description_content_type="text/markdown", 20 | author="Simon Willison", 21 | url="https://github.com/simonw/datasette-jupyterlite", 22 | project_urls={ 23 | "Issues": "https://github.com/simonw/datasette-jupyterlite/issues", 24 | "CI": "https://github.com/simonw/datasette-jupyterlite/actions", 25 | "Changelog": "https://github.com/simonw/datasette-jupyterlite/releases", 26 | }, 27 | license="Apache License, Version 2.0", 28 | version=VERSION, 29 | packages=["datasette_jupyterlite"], 30 | entry_points={"datasette": ["jupyterlite = datasette_jupyterlite"]}, 31 | install_requires=["datasette", "jupyterlite", "importlib-resources"], 32 | extras_require={"test": ["pytest", "pytest-asyncio"]}, 33 | tests_require=["datasette-jupyterlite[test]"], 34 | package_data={"datasette_jupyterlite": ["static/*"]}, 35 | python_requires=">=3.7", 36 | ) 37 | -------------------------------------------------------------------------------- /tests/test_jupyterlite.py: -------------------------------------------------------------------------------- 1 | from datasette.app import Datasette 2 | import pytest 3 | 4 | 5 | @pytest.mark.asyncio 6 | async def test_serve_jupyterlite(): 7 | datasette = Datasette([]) 8 | response = await datasette.client.get("/jupyterlite/") 9 | assert response.status_code == 200 10 | assert "# jupyter-lite-root" in response.text 11 | 12 | 13 | @pytest.mark.asyncio 14 | async def test_menu_link(): 15 | datasette = Datasette([]) 16 | response = await datasette.client.get("/") 17 | assert response.status_code == 200 18 | assert '
  • JupyterLite
  • ' in response.text 19 | --------------------------------------------------------------------------------