Skip to content

Commit

Permalink
Update to JupyterHub 4 and TLJH 1.0, use pytest-jupyterhub (#65)
Browse files Browse the repository at this point in the history
* Use `pytest-jupyterhub`

* Fix fixture cleanup

* Fix JupyterHub 4 warning

* Update docs for TLJH 1.0

* Test JupyterHub 4 only

* Remove notebook from dev dependencies

* Update readme

* Remove unused imports

* Update `pyproject.toml`

---------

Co-authored-by: Duc Trung Le <leductrungxf@gmail.com>
  • Loading branch information
jtpio and trungleduc authored Mar 12, 2024
1 parent 1fb90a6 commit cda9600
Show file tree
Hide file tree
Showing 10 changed files with 31 additions and 76 deletions.
5 changes: 0 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ jobs:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10"]
jupyterhub-version: [latest, 1.2.2]

steps:
- uses: actions/checkout@v2
Expand All @@ -32,10 +31,6 @@ jobs:
python -m pip install --upgrade pip setuptools
python -m pip install -r dev-requirements.txt
python -m pip install -e .
- name: Downgrade jupyterhub
if: ${{ matrix.jupyterhub-version != 'latest' }}
run: |
python -m pip install -U 'jupyterhub==${{ matrix.jupyterhub-version }}'
- name: Run Tests
run: |
python -m pytest --cov
10 changes: 7 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

TLJH plugin to build and use Docker images as user environments. The Docker images are built using [`repo2docker`](https://repo2docker.readthedocs.io/en/latest/).

## Requirements

This plugin requires [The Littlest JupyterHub](https://tljh.jupyter.org) 1.0 or later (running on JupyterHub 4+).

## Installation

During the [TLJH installation process](http://tljh.jupyter.org/en/latest/install/index.html), use the following post-installation script:
Expand All @@ -20,17 +24,17 @@ sudo apt update && sudo apt install -y docker-ce
# pull the repo2docker image
sudo docker pull quay.io/jupyterhub/repo2docker:main

# install TLJH
curl https://raw.githubusercontent.com/jupyterhub/the-littlest-jupyterhub/master/bootstrap/bootstrap.py \
# install TLJH 1.0
curl https://tljh.jupyter.org/bootstrap.py
| sudo python3 - \
--version 1.0.0 \
--admin test:test \
--plugin git+https://github.com/plasmabio/tljh-repo2docker@master
```

Refer to [The Littlest JupyterHub documentation](http://tljh.jupyter.org/en/latest/topic/customizing-installer.html?highlight=plugins#installing-tljh-plugins)
for more info on installing TLJH plugins.


## Usage

### List the environments
Expand Down
6 changes: 3 additions & 3 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
git+https://github.com/jupyterhub/the-littlest-jupyterhub
jupyterhub~=1.5
notebook<7
git+https://github.com/jupyterhub/the-littlest-jupyterhub@1.0.0
jupyterhub>=4,<5
pytest
pytest-aiohttp
pytest-asyncio
pytest-cov
pytest-jupyterhub
requests-mock
3 changes: 1 addition & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,7 @@ license = { file = "LICENSE" }
dependencies = [
"aiodocker~=0.19",
"dockerspawner~=12.1",
"jupyter_client~=6.1",
"sqlalchemy<2",
"jupyter_client>=6.1,<8"
]

[project.entry-points.tljh]
Expand Down
1 change: 1 addition & 0 deletions pytest.ini
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
[pytest]
python_files = test_*.py
asyncio_mode = auto
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__import__("setuptools").setup()
__import__("setuptools").setup()
6 changes: 3 additions & 3 deletions tljh_repo2docker/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

from aiodocker import Docker, DockerError
from jupyterhub.apihandlers import APIHandler
from jupyterhub.utils import admin_only
from jupyterhub.scopes import needs_scope
from tornado import web

from .docker import build_image
Expand All @@ -17,7 +17,7 @@ class BuildHandler(APIHandler):
"""

@web.authenticated
@admin_only
@needs_scope('admin-ui')
async def delete(self):
data = self.get_json_body()
name = data["name"]
Expand All @@ -31,7 +31,7 @@ async def delete(self):
self.finish(json.dumps({"status": "ok"}))

@web.authenticated
@admin_only
@needs_scope('admin-ui')
async def post(self):
data = self.get_json_body()
repo = data["repo"]
Expand Down
4 changes: 2 additions & 2 deletions tljh_repo2docker/images.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from inspect import isawaitable
from jupyterhub.handlers.base import BaseHandler
from jupyterhub.utils import admin_only
from jupyterhub.scopes import needs_scope
from tornado import web

from .docker import list_containers, list_images
Expand All @@ -12,7 +12,7 @@ class ImagesHandler(BaseHandler):
"""

@web.authenticated
@admin_only
@needs_scope('admin-ui')
async def get(self):
images = await list_images()
containers = await list_containers()
Expand Down
5 changes: 2 additions & 3 deletions tljh_repo2docker/logs.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@

from aiodocker import Docker
from jupyterhub.apihandlers import APIHandler
from jupyterhub.utils import admin_only
from jupyterhub.scopes import needs_scope
from tornado import web
from tornado.ioloop import IOLoop
from tornado.iostream import StreamClosedError


Expand All @@ -13,7 +12,7 @@ class LogsHandler(APIHandler):
Expose a handler to follow the build logs.
"""
@web.authenticated
@admin_only
@needs_scope('admin-ui')
async def get(self, name):
self.set_header("Content-Type", "text/event-stream")
self.set_header("Cache-Control", "no-cache")
Expand Down
65 changes: 11 additions & 54 deletions tljh_repo2docker/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,25 +1,8 @@
import asyncio
import os
import sys

import pytest

from aiodocker import Docker, DockerError
from jupyterhub.tests.conftest import (
io_loop,
event_loop,
db,
pytest_collection_modifyitems,
)
from jupyterhub.tests.mocking import MockHub
from tljh_repo2docker import tljh_custom_jupyterhub_config
from traitlets import Bunch

class DummyConfig:
def __getattr__(self, k):
if k not in self.__dict__:
self.__dict__[k] = Bunch()
return self.__dict__[k]
from traitlets.config import Config


async def remove_docker_image(image_name):
Expand Down Expand Up @@ -50,43 +33,17 @@ def image_name():
return "tljh-repo2docker-test:HEAD"


@pytest.fixture(scope='module')
def app(request, io_loop):
"""
Adapted from:
https://github.com/jupyterhub/jupyterhub/blob/8a3790b01ff944c453ffcc0486149e2a58ffabea/jupyterhub/tests/conftest.py#L74
"""

mocked_app = MockHub.instance()
c = DummyConfig()
c.JupyterHub = mocked_app
tljh_custom_jupyterhub_config(c)

async def make_app():
await mocked_app.initialize([])
await mocked_app.start()

def fin():
# disconnect logging during cleanup because pytest closes captured FDs prematurely
mocked_app.log.handlers = []
MockHub.clear_instance()
try:
mocked_app.stop()
except Exception as e:
print("Error stopping Hub: %s" % e, file=sys.stderr)
@pytest.fixture
async def app(hub_app):
config = Config()
tljh_custom_jupyterhub_config(config)

request.addfinalizer(fin)
io_loop.run_sync(make_app)
return mocked_app
app = await hub_app(config=config)
return app


@pytest.fixture(autouse=True)
def remove_all_test_images(image_name, generated_image_name, io_loop):
try:
yield
finally:
async def _clean():
await remove_docker_image(image_name)
await remove_docker_image(generated_image_name)

io_loop.run_sync(_clean)
async def remove_all_test_images(image_name, generated_image_name, app):
yield
await remove_docker_image(image_name)
await remove_docker_image(generated_image_name)

0 comments on commit cda9600

Please sign in to comment.