diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d866aff..a42c5ae 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -31,12 +31,13 @@ jobs: fail-fast: false matrix: include: - - python-version: "3.8" - pip-install-spec: "jupyterhub==2.3.1 sqlalchemy==1.*" + # jupyterhub 4 works with pytest-asyncio 0.25 + # but requires some deprecated features, + # so don't let it upgrade further - python-version: "3.9" - pip-install-spec: "jupyterhub==3.*" + pip-install-spec: "jupyterhub==4.* sqlalchemy==1.* pytest-asyncio==0.25.*" - python-version: "3.11" - pip-install-spec: "jupyterhub==4.*" + pip-install-spec: "jupyterhub==4.* pytest-asyncio==0.25.*" test-variation: podman - python-version: "3.12" pip-install-spec: "jupyterhub==5.*" diff --git a/README.md b/README.md index 9ed0db6..46caeb2 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ for more information about features and usage. ## Prerequisites -Python 3.8 or above and JupyterHub 2.3.1 or above is required. +Python 3.9 or above and JupyterHub 4 or above is required. ## Installation diff --git a/dev-requirements.txt b/dev-requirements.txt index b899b3e..5f57b65 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,6 +1,5 @@ netifaces notebook<7 pytest>=3.6 -# FIXME: unpin pytest-asyncio -pytest-asyncio>=0.17,<0.23 +pytest-asyncio>=0.25 pytest-cov diff --git a/pyproject.toml b/pyproject.toml index 41a2d93..7b76a17 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,6 @@ profile = "black" [tool.black] skip-string-normalization = true target_version = [ - "py38", "py39", "py310", "py311", @@ -38,6 +37,7 @@ target_version = [ [tool.pytest.ini_options] addopts = "--verbose --color=yes --durations=10 --maxfail=1" asyncio_mode = "auto" +asyncio_default_fixture_loop_scope = "module" testpaths = ["tests"] # These markers are registered to avoid warnings triggered by importing from # jupyterhub.tests.test_api. diff --git a/requirements.txt b/requirements.txt index 2e2c051..8964868 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ docker escapism -jupyterhub>=2.3.1 +jupyterhub>=4 diff --git a/setup.py b/setup.py index 6fde6fc..ef81d24 100644 --- a/setup.py +++ b/setup.py @@ -71,7 +71,7 @@ def run(self): 'docker-swarm = dockerspawner:SwarmSpawner', ], }, - python_requires=">=3.8", + python_requires=">=3.9", cmdclass={ 'bdist_egg': bdist_egg if 'bdist_egg' in sys.argv else bdist_egg_disabled, }, diff --git a/tests/conftest.py b/tests/conftest.py index b79dbbe..cd266c6 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -9,16 +9,20 @@ import jupyterhub import netifaces import pytest +import pytest_asyncio from docker import from_env as docker_from_env from docker.errors import APIError from jupyterhub import version_info as jh_version_info from jupyterhub.tests.conftest import app as jupyterhub_app # noqa: F401 -from jupyterhub.tests.conftest import event_loop # noqa: F401 from jupyterhub.tests.conftest import io_loop # noqa: F401 from jupyterhub.tests.conftest import ssl_tmpdir # noqa: F401 from jupyterhub.tests.conftest import user # noqa: F401 from jupyterhub.tests.mocking import MockHub +if 'event_loop' in inspect.signature(io_loop).parameters: + # older JupyterHub (< 5.x), io_loop requires deprecated event_loop + from jupyterhub.tests.conftest import event_loop # noqa: F401 + from dockerspawner import DockerSpawner, SwarmSpawner, SystemUserSpawner # import base jupyterhub fixtures @@ -46,16 +50,19 @@ def pytest_collection_modifyitems(items): - """This function is automatically run by pytest passing all collected test - functions. - - We use it to add asyncio marker to all async tests and assert we don't use - test functions that are async generators which wouldn't make sense. - """ - for item in items: - if inspect.iscoroutinefunction(item.obj): - item.add_marker('asyncio') - assert not inspect.isasyncgenfunction(item.obj) + # apply loop_scope="module" to all async tests by default + # this can be hopefully be removed in favor of config if + # https://github.com/pytest-dev/pytest-asyncio/issues/793 + # is addressed + pytest_asyncio_tests = ( + item for item in items if pytest_asyncio.is_async_test(item) + ) + asyncio_scope_marker = pytest.mark.asyncio(loop_scope="module") + for async_test in pytest_asyncio_tests: + # add asyncio marker _if_ not already present + asyncio_marker = async_test.get_closest_marker('asyncio') + if not asyncio_marker or not asyncio_marker.kwargs: + async_test.add_marker(asyncio_scope_marker, append=False) @pytest.fixture @@ -89,9 +96,10 @@ def dockerspawner_configured_app(app, named_servers): @pytest.fixture def swarmspawner_configured_app(app, named_servers): """Configure JupyterHub to use DockerSpawner""" - with mock.patch.dict( - app.tornado_settings, {"spawner_class": SwarmSpawner} - ), mock.patch.dict(app.config.SwarmSpawner, {"network_name": "bridge"}): + with ( + mock.patch.dict(app.tornado_settings, {"spawner_class": SwarmSpawner}), + mock.patch.dict(app.config.SwarmSpawner, {"network_name": "bridge"}), + ): yield app