diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f9cc628..80982cf 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -47,6 +47,10 @@ jobs: steps: - uses: actions/checkout@v3 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 + - name: Install Dependencies + run: | + sudo apt-get update + sudo apt-get install enchant-2 # for spell checking - run: hatch run docs:build test_lint: @@ -59,7 +63,7 @@ jobs: run: | hatch run typing:test hatch run lint:style - pipx run 'validate-pyproject[all]' pyproject.toml + pipx run interrogate -v . pipx run doc8 --max-line-length=200 --ignore-path=docs/source/other/full-config.rst test_minimum_versions: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ff708b2..55e8e74 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -5,17 +5,18 @@ repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v4.4.0 hooks: - - id: end-of-file-fixer - id: check-case-conflict + - id: check-ast + - id: check-docstring-first - id: check-executables-have-shebangs - - id: requirements-txt-fixer - id: check-added-large-files - id: check-case-conflict + - id: check-merge-conflict + - id: check-json - id: check-toml - id: check-yaml - id: debug-statements - - id: forbid-new-submodules - - id: check-builtin-literals + - id: end-of-file-fixer - id: trailing-whitespace - repo: https://github.com/python-jsonschema/check-jsonschema @@ -34,7 +35,7 @@ repos: - id: black - repo: https://github.com/charliermarsh/ruff-pre-commit - rev: v0.0.165 + rev: v0.0.189 hooks: - id: ruff args: ["--fix"] diff --git a/docs/conf.py b/docs/conf.py index 76731d4..ddab80d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -32,6 +32,14 @@ "sphinx.ext.autodoc", ] +try: + import enchant # type:ignore # noqa + + extensions += ["sphinxcontrib.spelling"] +except ImportError: + pass + + # Autodoc fixtures # If true, the current module name will be prepended to all description # unit titles (such as .. function::). diff --git a/pyproject.toml b/pyproject.toml index 00738b2..30ee3d5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,6 +38,7 @@ docs = [ "myst_parser", "pydata_sphinx_theme", "Sphinx", + "sphinxcontrib-spelling" ] client = [ "jupyter_client>=7.4.0", @@ -52,9 +53,9 @@ test = [ "pytest-timeout" ] lint = [ - "black>=22.6.0", + "black[jupyter]==22.10.0", "mdformat>0.7", - "ruff>=0.0.156", + "ruff==0.0.189", ] typing = [ "mypy>=0.990" @@ -216,3 +217,13 @@ unfixable = [ # B007 Loop control variable `i` not used within the loop body. # N802 Function name `assertIn` should be lowercase "tests/*" = ["B011", "F841", "C408", "E402", "T201", "B007", "N802"] + +[tool.interrogate] +ignore-init-module=true +ignore-private=true +ignore-semiprivate=true +ignore-property-decorators=true +ignore-nested-functions=true +ignore-nested-classes=true +fail-under=100 +exclude = ["docs", "tests"] diff --git a/pytest_jupyter/_version.py b/pytest_jupyter/_version.py index d3b2bce..bd949e5 100644 --- a/pytest_jupyter/_version.py +++ b/pytest_jupyter/_version.py @@ -1,3 +1,4 @@ +"""Version info for pytest_jupyter.""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. diff --git a/pytest_jupyter/echo_kernel.py b/pytest_jupyter/echo_kernel.py index 4f61168..6a9f7ad 100644 --- a/pytest_jupyter/echo_kernel.py +++ b/pytest_jupyter/echo_kernel.py @@ -1,3 +1,4 @@ +"""A simple echo kernel.""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. @@ -8,6 +9,8 @@ class EchoKernel(Kernel): + """An echo kernel.""" + implementation = "Echo" implementation_version = "1.0" language = "echo" @@ -22,6 +25,7 @@ class EchoKernel(Kernel): def do_execute( self, code, silent, store_history=True, user_expressions=None, allow_stdin=False ): + """Execute code on the kernel.""" if not silent: stream_content = {"name": "stdout", "text": code} self.send_response(self.iopub_socket, "stream", stream_content) @@ -45,6 +49,8 @@ def do_execute( class EchoKernelApp(IPKernelApp): + """An app for the echo kernel.""" + kernel_class = EchoKernel diff --git a/pytest_jupyter/jupyter_client.py b/pytest_jupyter/jupyter_client.py index 21de239..266a1f1 100644 --- a/pytest_jupyter/jupyter_client.py +++ b/pytest_jupyter/jupyter_client.py @@ -1,3 +1,4 @@ +"""Fixtures for use with jupyter_client and downstream.""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. @@ -24,6 +25,7 @@ @pytest.fixture def jp_zmq_context(): + """Get a zmq context.""" import zmq ctx = zmq.asyncio.Context() @@ -33,10 +35,12 @@ def jp_zmq_context(): @pytest.fixture def jp_start_kernel(jp_environ, jp_asyncio_loop): + """Get a function to a kernel and clean up resources when done.""" kms = [] kcs = [] async def inner(kernel_name=NATIVE_KERNEL_NAME, **kwargs): + """A function used to start a kernel.""" km, kc = await start_new_async_kernel(kernel_name=kernel_name, **kwargs) kms.append(km) kcs.append(kc) diff --git a/pytest_jupyter/jupyter_core.py b/pytest_jupyter/jupyter_core.py index 07a1a27..ca33f5b 100644 --- a/pytest_jupyter/jupyter_core.py +++ b/pytest_jupyter/jupyter_core.py @@ -1,3 +1,4 @@ +"""Fixtures for use with jupyter core and downstream.""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. import asyncio @@ -35,6 +36,7 @@ @pytest.fixture def jp_asyncio_loop(): + """Get an asyncio loop.""" if os.name == "nt": asyncio.set_event_loop_policy( asyncio.WindowsSelectorEventLoopPolicy() # type:ignore[attr-defined] @@ -51,6 +53,7 @@ def io_loop(jp_asyncio_loop): if tornado is not installed.""" async def get_tornado_loop(): + """Asynchronously get a tornado loop.""" try: from tornado.ioloop import IOLoop @@ -111,11 +114,13 @@ def jp_env_config_path(tmp_path): @pytest.fixture() def jp_kernel_dir(jp_data_dir): + """Get the directory for kernel specs.""" return mkdir(jp_data_dir, "kernels") @pytest.fixture def echo_kernel_spec(jp_kernel_dir): + """Install a kernel spec for the echo kernel.""" test_dir = Path(jp_kernel_dir) / "echo" test_dir.mkdir(parents=True, exist_ok=True) argv = [sys.executable, "-m", "pytest_jupyter.echo_kernel", "-f", "{connection_file}"] diff --git a/pytest_jupyter/jupyter_server.py b/pytest_jupyter/jupyter_server.py index 9678f09..1897cca 100644 --- a/pytest_jupyter/jupyter_server.py +++ b/pytest_jupyter/jupyter_server.py @@ -1,3 +1,4 @@ +"""Fixtures for use with jupyter server and downstream.""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. @@ -59,6 +60,7 @@ def http_server(io_loop, http_server_port, jp_web_app): """Start a tornado HTTP server that listens on all available interfaces.""" async def get_server(): + """Get a server asynchronously.""" server = tornado.httpserver.HTTPServer(jp_web_app) server.add_socket(http_server_port[0]) return server @@ -385,6 +387,7 @@ def inner(nbpath): @pytest.fixture(autouse=True) def jp_server_cleanup(jp_asyncio_loop): + """Automatically cleans up server resources.""" yield app: ServerApp = ServerApp.instance() try: @@ -422,6 +425,7 @@ async def _(url, **fetch_kwargs): @pytest.fixture def jp_server_auth_core_resources(): + """The core auth resources for use with a server.""" modules = [] for mod_name in JUPYTER_SERVICE_HANDLERS.values(): if mod_name: @@ -438,10 +442,13 @@ def jp_server_auth_core_resources(): @pytest.fixture def jp_server_auth_resources(jp_server_auth_core_resources): + """The auth resources used by the server.""" return jp_server_auth_core_resources class _Authorizer(Authorizer): + """A custom authorizer class for testing.""" + # Set these class attributes from within a test # to verify that they match the arguments passed # by the REST API. @@ -484,6 +491,7 @@ def normalize_url(self, path): return path def is_authorized(self, handler, user, action, resource): + """Test if a request is authorized.""" # Parse Request if isinstance(handler, WebSocketHandler): method = "WEBSOCKET" @@ -511,6 +519,7 @@ def is_authorized(self, handler, user, action, resource): @pytest.fixture def jp_server_authorizer(jp_server_auth_resources): + """An authorizer for the server.""" auth_klass = _Authorizer auth_klass._default_regex_mapping = jp_server_auth_resources return auth_klass diff --git a/pytest_jupyter/pytest_tornasync.py b/pytest_jupyter/pytest_tornasync.py index 3b28fa1..486c3ee 100644 --- a/pytest_jupyter/pytest_tornasync.py +++ b/pytest_jupyter/pytest_tornasync.py @@ -1,6 +1,6 @@ -# Vendored fork of pytest_tornasync from -# https://github.com/eukaryote/pytest-tornasync/blob/9f1bdeec3eb5816e0183f975ca65b5f6f29fbfbb/src/pytest_tornasync/plugin.py - +"""Vendored fork of pytest_tornasync from + https://github.com/eukaryote/pytest-tornasync/blob/9f1bdeec3eb5816e0183f975ca65b5f6f29fbfbb/src/pytest_tornasync/plugin.py +""" from contextlib import closing from inspect import iscoroutinefunction @@ -16,12 +16,14 @@ @pytest.hookimpl(tryfirst=True) def pytest_pycollect_makeitem(collector, name, obj): + """Custom pytest collection hook.""" if collector.funcnamefilter(name) and iscoroutinefunction(obj): return list(collector._genfunctions(name, obj)) @pytest.hookimpl(tryfirst=True) def pytest_pyfunc_call(pyfuncitem): + """Custom pytest function call hook.""" funcargs = pyfuncitem.funcargs testargs = {arg: funcargs[arg] for arg in pyfuncitem._fixtureinfo.argnames} @@ -53,6 +55,7 @@ def http_server_client(http_server, io_loop): """ async def get_client(): + """Get a client.""" return AsyncHTTPServerClient(http_server=http_server) client = io_loop.run_sync(get_client) @@ -61,7 +64,10 @@ async def get_client(): class AsyncHTTPServerClient(SimpleAsyncHTTPClient): + """An async http server client.""" + def initialize(self, *, http_server=None): + """Initialize the client.""" super().initialize() self._http_server = http_server @@ -73,11 +79,14 @@ def fetch(self, path, **kwargs): return super().fetch(self.get_url(path), **kwargs) def get_protocol(self): + """Get the protocol for the client.""" return "http" def get_http_port(self): + """Get a port for the client.""" for sock in self._http_server._sockets.values(): return sock.getsockname()[1] def get_url(self, path): + """Get the url for the client.""" return f"{self.get_protocol()}://127.0.0.1:{self.get_http_port()}{path}" diff --git a/pytest_jupyter/utils.py b/pytest_jupyter/utils.py index 97b70b7..305dca2 100644 --- a/pytest_jupyter/utils.py +++ b/pytest_jupyter/utils.py @@ -1,8 +1,10 @@ +"""Utilities for pytest-jupyter.""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. def mkdir(tmp_path, *parts): + """Make a directory given extra path parts.""" path = tmp_path.joinpath(*parts) if not path.exists(): path.mkdir(parents=True)