diff --git a/.github/workflows/python-tests.yml b/.github/workflows/python-tests.yml index 3fe296fcab..658c073588 100644 --- a/.github/workflows/python-tests.yml +++ b/.github/workflows/python-tests.yml @@ -79,8 +79,8 @@ jobs: steps: - uses: actions/checkout@v4 - uses: jupyterlab/maintainer-tools/.github/actions/base-setup@v1 - - name: Run Linters - run: | + - name: Run Linters + run: | hatch run typing:test hatch run lint:style pipx run interrogate -v . diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b36581869e..fa47c7e226 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,5 +1,6 @@ ci: autoupdate_schedule: monthly + autoupdate_commit_msg: "chore: update pre-commit hooks" repos: - repo: https://github.com/pre-commit/pre-commit-hooks @@ -28,14 +29,47 @@ repos: rev: 0.7.17 hooks: - id: mdformat + additional_dependencies: + [mdformat-gfm, mdformat-frontmatter, mdformat-footnote] - - repo: https://github.com/psf/black + - repo: https://github.com/pre-commit/mirrors-prettier + rev: "v3.0.2" + hooks: + - id: prettier + types_or: [yaml, html, json] + + - repo: https://github.com/adamchainz/blacken-docs + rev: "1.16.0" + hooks: + - id: blacken-docs + additional_dependencies: [black==23.7.0] + + - repo: https://github.com/psf/black-pre-commit-mirror rev: 23.7.0 hooks: - id: black + - repo: https://github.com/codespell-project/codespell + rev: "v2.2.5" + hooks: + - id: codespell + args: ["-L", "sur,nd"] + + - repo: https://github.com/pre-commit/pygrep-hooks + rev: "v1.10.0" + hooks: + - id: rst-backticks + - id: rst-directive-colons + - id: rst-inline-touching-normal + - repo: https://github.com/astral-sh/ruff-pre-commit rev: v0.0.287 hooks: - id: ruff - args: ["--fix"] + args: ["--fix", "--show-fixes"] + + - repo: https://github.com/scientific-python/cookie + rev: "2023.08.23" + hooks: + - id: sp-repo-review + additional_dependencies: ["repo-review[cli]"] diff --git a/CHANGELOG.md b/CHANGELOG.md index ba10a4b2c1..6361fd9706 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -271,7 +271,7 @@ All notable changes to this project will be documented in this file. ### Bugs fixed -- Reapply preferred_dir fix, now with better backwards compatability [#1162](https://github.com/jupyter-server/jupyter_server/pull/1162) ([@vidartf](https://github.com/vidartf)) +- Reapply preferred_dir fix, now with better backwards compatibility [#1162](https://github.com/jupyter-server/jupyter_server/pull/1162) ([@vidartf](https://github.com/vidartf)) ### Maintenance and upkeep improvements @@ -1055,7 +1055,7 @@ All notable changes to this project will be documented in this file. ### Bugs fixed -- Regression in connection URL calcuation in ServerApp [#761](https://github.com/jupyter-server/jupyter_server/pull/761) ([@jhamet93](https://github.com/jhamet93)) +- Regression in connection URL calculation in ServerApp [#761](https://github.com/jupyter-server/jupyter_server/pull/761) ([@jhamet93](https://github.com/jhamet93)) - Include explicit package data [#757](https://github.com/jupyter-server/jupyter_server/pull/757) ([@blink1073](https://github.com/blink1073)) - Ensure terminal cwd exists [#755](https://github.com/jupyter-server/jupyter_server/pull/755) ([@fcollonval](https://github.com/fcollonval)) - make 'cwd' param for TerminalManager absolute [#749](https://github.com/jupyter-server/jupyter_server/pull/749) ([@rccern](https://github.com/rccern)) @@ -1947,7 +1947,7 @@ This was a broken release and was yanked from PyPI. ### Added -- ([#191](https://github.com/jupyter/jupyter_server/pull/191)) Async kernel managment is now possible using the `AsyncKernelManager` from `jupyter_client` +- ([#191](https://github.com/jupyter/jupyter_server/pull/191)) Async kernel management is now possible using the `AsyncKernelManager` from `jupyter_client` - ([#201](https://github.com/jupyter/jupyter_server/pull/201)) Parameters can now be passed to new terminals created by the `terminals` REST API. ### Changed diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst index 3398e39e74..8ff3322548 100644 --- a/CONTRIBUTING.rst +++ b/CONTRIBUTING.rst @@ -40,11 +40,11 @@ from any directory in your system with:: Code Styling and Quality Checks ------------------------------- -`jupyter_server` has adopted automatic code formatting so you shouldn't +``jupyter_server`` has adopted automatic code formatting so you shouldn't need to worry too much about your code style. As long as your code is valid, the pre-commit hook should take care of how it should look. -`pre-commit` and its associated hooks will automatically be installed when +``pre-commit`` and its associated hooks will automatically be installed when you run ``pip install -e ".[test]"`` To install ``pre-commit`` hook manually, run the following:: diff --git a/docs/source/conf.py b/docs/source/conf.py index 2ed837b987..29db736e96 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -44,7 +44,7 @@ ] try: - import enchant # type:ignore # noqa + import enchant # type:ignore[import] # noqa extensions += ["sphinxcontrib.spelling"] except ImportError: diff --git a/docs/source/developers/contents.rst b/docs/source/developers/contents.rst index 28d2a33334..cbf9d6ca6f 100644 --- a/docs/source/developers/contents.rst +++ b/docs/source/developers/contents.rst @@ -109,39 +109,39 @@ model. There are three model types: **notebook**, **file**, and **directory**. # Notebook Model with Content { - 'content': { - 'metadata': {}, - 'nbformat': 4, - 'nbformat_minor': 0, - 'cells': [ + "content": { + "metadata": {}, + "nbformat": 4, + "nbformat_minor": 0, + "cells": [ { - 'cell_type': 'markdown', - 'metadata': {}, - 'source': 'Some **Markdown**', + "cell_type": "markdown", + "metadata": {}, + "source": "Some **Markdown**", }, ], }, - 'created': datetime(2015, 7, 25, 19, 50, 19, 19865), - 'format': 'json', - 'last_modified': datetime(2015, 7, 25, 19, 50, 19, 19865), - 'mimetype': None, - 'name': 'a.ipynb', - 'path': 'foo/a.ipynb', - 'type': 'notebook', - 'writable': True, + "created": datetime(2015, 7, 25, 19, 50, 19, 19865), + "format": "json", + "last_modified": datetime(2015, 7, 25, 19, 50, 19, 19865), + "mimetype": None, + "name": "a.ipynb", + "path": "foo/a.ipynb", + "type": "notebook", + "writable": True, } # Notebook Model without Content { - 'content': None, - 'created': datetime.datetime(2015, 7, 25, 20, 17, 33, 271931), - 'format': None, - 'last_modified': datetime.datetime(2015, 7, 25, 20, 17, 33, 271931), - 'mimetype': None, - 'name': 'a.ipynb', - 'path': 'foo/a.ipynb', - 'type': 'notebook', - 'writable': True + "content": None, + "created": datetime.datetime(2015, 7, 25, 20, 17, 33, 271931), + "format": None, + "last_modified": datetime.datetime(2015, 7, 25, 20, 17, 33, 271931), + "mimetype": None, + "name": "a.ipynb", + "path": "foo/a.ipynb", + "type": "notebook", + "writable": True, } @@ -227,21 +227,28 @@ return for a more complete implementation. class NoOpCheckpoints(GenericCheckpointsMixin, Checkpoints): """requires the following methods:""" + def create_file_checkpoint(self, content, format, path): - """ -> checkpoint model""" + """-> checkpoint model""" + def create_notebook_checkpoint(self, nb, path): - """ -> checkpoint model""" + """-> checkpoint model""" + def get_file_checkpoint(self, checkpoint_id, path): - """ -> {'type': 'file', 'content': , 'format': {'text', 'base64'}}""" + """-> {'type': 'file', 'content': , 'format': {'text', 'base64'}}""" + def get_notebook_checkpoint(self, checkpoint_id, path): - """ -> {'type': 'notebook', 'content': }""" + """-> {'type': 'notebook', 'content': }""" + def delete_checkpoint(self, checkpoint_id, path): """deletes a checkpoint for a file""" + def list_checkpoints(self, path): """returns a list of checkpoint models for a given file, default just does one per file """ return [] + def rename_checkpoint(self, checkpoint_id, old_path, new_path): """renames checkpoint from old path to new path""" diff --git a/docs/source/developers/extensions.rst b/docs/source/developers/extensions.rst index bd5ff5a586..5c27d25747 100644 --- a/docs/source/developers/extensions.rst +++ b/docs/source/developers/extensions.rst @@ -36,8 +36,8 @@ The easiest way to add endpoints and handle incoming requests is to subclass the from jupyter_server.base.handlers import JupyterHandler import tornado - class MyExtensionHandler(JupyterHandler): + class MyExtensionHandler(JupyterHandler): @tornado.web.authenticated def get(self): ... @@ -57,10 +57,8 @@ Then add this handler to Jupyter Server's Web Application through the ``_load_ju """ This function is called when the extension is loaded. """ - handlers = [ - ('/myextension/hello', MyExtensionHandler) - ] - serverapp.web_app.add_handlers('.*$', handlers) + handlers = [("/myextension/hello", MyExtensionHandler)] + serverapp.web_app.add_handlers(".*$", handlers) Making an extension discoverable @@ -79,19 +77,13 @@ Usually, this requires a ``module`` key with the import path to the extension's Returns a list of dictionaries with metadata describing where to find the `_load_jupyter_server_extension` function. """ - return [ - { - "module": "my_extension" - } - ] + return [{"module": "my_extension"}] Second, add the extension to the ServerApp's ``jpserver_extensions`` trait. This can be manually added by users in their ``jupyter_server_config.py`` file, .. code-block:: python - c.ServerApp.jpserver_extensions = { - "my_extension": True - } + c.ServerApp.jpserver_extensions = {"my_extension": True} or loaded from a JSON file in the ``jupyter_server_config.d`` directory under one of `Jupyter's paths`_. (See the `Distributing a server extension`_ section @@ -100,13 +92,7 @@ it.) .. code-block:: python - { - "ServerApp": { - "jpserver_extensions": { - "my_extension": true - } - } - } + {"ServerApp": {"jpserver_extensions": {"my_extension": true}}} Authoring a configurable extension application @@ -136,7 +122,6 @@ The basic structure of an ExtensionApp is shown below: class MyExtensionApp(ExtensionApp): - # -------------- Required traits -------------- name = "myextension" default_url = "/myextension" @@ -156,7 +141,7 @@ The basic structure of an ExtensionApp is shown below: ... # Update the self.settings trait to pass extra # settings to the underlying Tornado Web Application. - self.settings.update({'':...}) + self.settings.update({"": ...}) def initialize_handlers(self): ... @@ -211,7 +196,6 @@ Jupyter Server provides a convenient mixin class for adding these properties to class MyExtensionHandler(ExtensionHandlerMixin, JupyterHandler): - @tornado.web.authenticated def get(self): ... @@ -251,16 +235,14 @@ templates from the Jinja templating environment created by the ``ExtensionApp``. from jupyter_server.base.handlers import JupyterHandler from jupyter_server.extension.handler import ( ExtensionHandlerMixin, - ExtensionHandlerJinjaMixin + ExtensionHandlerJinjaMixin, ) import tornado + class MyExtensionHandler( - ExtensionHandlerMixin, - ExtensionHandlerJinjaMixin, - JupyterHandler + ExtensionHandlerMixin, ExtensionHandlerJinjaMixin, JupyterHandler ): - @tornado.web.authenticated def get(self): ... @@ -288,12 +270,7 @@ To make an ``ExtensionApp`` discoverable by Jupyter Server, add the ``app`` key+ Returns a list of dictionaries with metadata describing where to find the `_load_jupyter_server_extension` function. """ - return [ - { - "module": "myextension", - "app": MyExtensionApp - } - ] + return [{"module": "myextension", "app": MyExtensionApp}] Launching an ``ExtensionApp`` @@ -315,13 +292,11 @@ To make your extension executable from anywhere on your system, point an entry-p setup( - name='myfrontend', - ... + name="myfrontend", + # ... entry_points={ - 'console_scripts': [ - 'jupyter-myextension = myextension:launch_instance' - ] - } + "console_scripts": ["jupyter-myextension = myextension:launch_instance"] + }, ) ``ExtensionApp`` as a classic Notebook server extension @@ -353,13 +328,9 @@ Putting it all together, authors can distribute their extension following this s # Found in the __init__.py of package + def _jupyter_server_extension_points(): - return [ - { - "module": "myextension.app", - "app": MyExtensionApp - } - ] + return [{"module": "myextension.app", "app": MyExtensionApp}] 2. Create an extension by writing a ``_load_jupyter_server_extension()`` function or subclassing ``ExtensionApp``. This is where the extension logic will live (i.e. custom extension handlers, config, etc). See the sections above for more information on how to create an extension. @@ -412,15 +383,14 @@ Putting it all together, authors can distribute their extension following this s setup( name="myextension", - ... + # ... include_package_data=True, data_files=[ ( "etc/jupyter/jupyter_server_config.d", - ["jupyter-config/jupyter_server_config.d/myextension.json"] + ["jupyter-config/jupyter_server_config.d/myextension.json"], ), - ] - + ], ) @@ -452,6 +422,7 @@ There are a few key steps to make this happen: def load_jupyter_server_extension(nb_server_app): ... + # Reference the old function name with the new function name. _load_jupyter_server_extension = load_jupyter_server_extension @@ -494,19 +465,18 @@ There are a few key steps to make this happen: setup( name="myextension", - ... + # ... include_package_data=True, data_files=[ ( "etc/jupyter/jupyter_server_config.d", - ["jupyter-config/jupyter_server_config.d/myextension.json"] + ["jupyter-config/jupyter_server_config.d/myextension.json"], ), ( "etc/jupyter/jupyter_notebook_config.d", - ["jupyter-config/jupyter_notebook_config.d/myextension.json"] + ["jupyter-config/jupyter_notebook_config.d/myextension.json"], ), - ] - + ], ) 3. (Optional) Point extension at the new favicon location. @@ -520,14 +490,13 @@ There are a few key steps to make this happen: .. code-block:: python def load_jupyter_server_extension(nb_server_app): - web_app = nb_server_app.web_app - host_pattern = '.*$' - base_url = web_app.settings['base_url'] + host_pattern = ".*$" + base_url = web_app.settings["base_url"] # Add custom extensions handler. custom_handlers = [ - ... + # ... ] # Favicon redirects. @@ -535,49 +504,74 @@ There are a few key steps to make this happen: ( url_path_join(base_url, "/static/favicons/favicon.ico"), RedirectHandler, - {"url": url_path_join(serverapp.base_url, "static/base/images/favicon.ico") + { + "url": url_path_join( + serverapp.base_url, "static/base/images/favicon.ico" + ) + }, ), ( url_path_join(base_url, "/static/favicons/favicon-busy-1.ico"), RedirectHandler, - {"url": url_path_join(serverapp.base_url, "static/base/images/favicon-busy-1.ico")} + { + "url": url_path_join( + serverapp.base_url, "static/base/images/favicon-busy-1.ico" + ) + }, ), ( url_path_join(base_url, "/static/favicons/favicon-busy-2.ico"), RedirectHandler, - {"url": url_path_join(serverapp.base_url, "static/base/images/favicon-busy-2.ico")} + { + "url": url_path_join( + serverapp.base_url, "static/base/images/favicon-busy-2.ico" + ) + }, ), ( url_path_join(base_url, "/static/favicons/favicon-busy-3.ico"), RedirectHandler, - {"url": url_path_join(serverapp.base_url, "static/base/images/favicon-busy-3.ico")} + { + "url": url_path_join( + serverapp.base_url, "static/base/images/favicon-busy-3.ico" + ) + }, ), ( url_path_join(base_url, "/static/favicons/favicon-file.ico"), RedirectHandler, - {"url": url_path_join(serverapp.base_url, "static/base/images/favicon-file.ico")} + { + "url": url_path_join( + serverapp.base_url, "static/base/images/favicon-file.ico" + ) + }, ), ( url_path_join(base_url, "/static/favicons/favicon-notebook.ico"), RedirectHandler, - {"url": url_path_join(serverapp.base_url, "static/base/images/favicon-notebook.ico")} + { + "url": url_path_join( + serverapp.base_url, "static/base/images/favicon-notebook.ico" + ) + }, ), ( url_path_join(base_url, "/static/favicons/favicon-terminal.ico"), RedirectHandler, - {"url": url_path_join(serverapp.base_url, "static/base/images/favicon-terminal.ico")} + { + "url": url_path_join( + serverapp.base_url, "static/base/images/favicon-terminal.ico" + ) + }, ), ( url_path_join(base_url, "/static/logo/logo.png"), RedirectHandler, - {"url": url_path_join(serverapp.base_url, "static/base/images/logo.png")} + {"url": url_path_join(serverapp.base_url, "static/base/images/logo.png")}, ), ] - web_app.add_handlers( - host_pattern, - custom_handlers + favicon_redirects - ) + web_app.add_handlers(host_pattern, custom_handlers + favicon_redirects) .. _`classic Notebook Server`: https://jupyter-notebook.readthedocs.io/en/v6.5.4/extending/handlers.html diff --git a/docs/source/developers/savehooks.rst b/docs/source/developers/savehooks.rst index f2998938ae..4a466ef830 100644 --- a/docs/source/developers/savehooks.rst +++ b/docs/source/developers/savehooks.rst @@ -51,6 +51,7 @@ A post-save hook to make a script equivalent whenever the notebook is saved _script_exporter = None + def script_post_save(model, os_path, contents_manager, **kwargs): """convert notebooks to Python script after save with nbconvert @@ -58,7 +59,7 @@ A post-save hook to make a script equivalent whenever the notebook is saved """ from nbconvert.exporters.script import ScriptExporter - if model['type'] != 'notebook': + if model["type"] != "notebook": return global _script_exporter @@ -69,14 +70,15 @@ A post-save hook to make a script equivalent whenever the notebook is saved log = contents_manager.log base, ext = os.path.splitext(os_path) - py_fname = base + '.py' + py_fname = base + ".py" script, resources = _script_exporter.from_filename(os_path) - script_fname = base + resources.get('output_extension', '.txt') + script_fname = base + resources.get("output_extension", ".txt") log.info("Saving script /%s", to_api_path(script_fname, contents_manager.root_dir)) - with io.open(script_fname, 'w', encoding='utf-8') as f: + with io.open(script_fname, "w", encoding="utf-8") as f: f.write(script) + c.FileContentsManager.post_save_hook = script_post_save diff --git a/docs/source/developers/websocket-protocols.rst b/docs/source/developers/websocket-protocols.rst index aeb5b44896..72017e86d7 100644 --- a/docs/source/developers/websocket-protocols.rst +++ b/docs/source/developers/websocket-protocols.rst @@ -81,11 +81,11 @@ The message can be deserialized by parsing ``msg`` as a JSON object (after decod .. code-block:: python msg = { - 'channel': channel, - 'header': header, - 'parent_header': parent_header, - 'metadata': metadata, - 'content': content + "channel": channel, + "header": header, + "parent_header": parent_header, + "metadata": metadata, + "content": content, } Then retrieving the channel name, and updating with the buffers, if any: @@ -147,7 +147,8 @@ Where: .. code-block:: python import json - channel = bin_msg[offset_0:offset_1].decode('utf-8') + + channel = bin_msg[offset_0:offset_1].decode("utf-8") header = json.loads(bin_msg[offset_1:offset_2]) parent_header = json.loads(bin_msg[offset_2:offset_3]) metadata = json.loads(bin_msg[offset_3:offset_4]) diff --git a/docs/source/operators/configuring-logging.rst b/docs/source/operators/configuring-logging.rst index ba68cdb6ef..1bb382fa73 100644 --- a/docs/source/operators/configuring-logging.rst +++ b/docs/source/operators/configuring-logging.rst @@ -27,18 +27,18 @@ A minimal example which logs Jupyter Server output to a file: .. code-block:: python c.ServerApp.logging_config = { - 'version': 1, - 'handlers': { - 'logfile': { - 'class': 'logging.FileHandler', - 'level': 'DEBUG', - 'filename': 'jupyter_server.log', + "version": 1, + "handlers": { + "logfile": { + "class": "logging.FileHandler", + "level": "DEBUG", + "filename": "jupyter_server.log", }, }, - 'loggers': { - 'ServerApp': { - 'level': 'DEBUG', - 'handlers': ['console', 'logfile'], + "loggers": { + "ServerApp": { + "level": "DEBUG", + "handlers": ["console", "logfile"], }, }, } @@ -69,49 +69,49 @@ An example which logs both Jupyter Server and Jupyter Lab output to a file: .. code-block:: python c.ServerApp.logging_config = { - 'version': 1, - 'handlers': { - 'logfile': { - 'class': 'logging.FileHandler', - 'level': 'DEBUG', - 'filename': 'jupyter_server.log', - 'formatter': 'my_format', + "version": 1, + "handlers": { + "logfile": { + "class": "logging.FileHandler", + "level": "DEBUG", + "filename": "jupyter_server.log", + "formatter": "my_format", }, }, - 'formatters': { - 'my_format': { - 'format': '%(asctime)s %(levelname)-8s %(name)-15s %(message)s', - 'datefmt': '%Y-%m-%d %H:%M:%S', + "formatters": { + "my_format": { + "format": "%(asctime)s %(levelname)-8s %(name)-15s %(message)s", + "datefmt": "%Y-%m-%d %H:%M:%S", }, }, - 'loggers': { - 'ServerApp': { - 'level': 'DEBUG', - 'handlers': ['console', 'logfile'], + "loggers": { + "ServerApp": { + "level": "DEBUG", + "handlers": ["console", "logfile"], }, }, } c.LabApp.logging_config = { - 'version': 1, - 'handlers': { - 'logfile': { - 'class': 'logging.FileHandler', - 'level': 'DEBUG', - 'filename': 'jupyter_server.log', - 'formatter': 'my_format', + "version": 1, + "handlers": { + "logfile": { + "class": "logging.FileHandler", + "level": "DEBUG", + "filename": "jupyter_server.log", + "formatter": "my_format", }, }, - 'formatters': { - 'my_format': { - 'format': '%(asctime)s %(levelname)-8s %(name)-15s %(message)s', - 'datefmt': '%Y-%m-%d %H:%M:%S', + "formatters": { + "my_format": { + "format": "%(asctime)s %(levelname)-8s %(name)-15s %(message)s", + "datefmt": "%Y-%m-%d %H:%M:%S", }, }, - 'loggers': { - 'LabApp': { - 'level': 'DEBUG', - 'handlers': ['console', 'logfile'], + "loggers": { + "LabApp": { + "level": "DEBUG", + "handlers": ["console", "logfile"], }, }, } diff --git a/docs/source/operators/public-server.rst b/docs/source/operators/public-server.rst index 78cdf19aa6..dc534ca832 100644 --- a/docs/source/operators/public-server.rst +++ b/docs/source/operators/public-server.rst @@ -10,8 +10,8 @@ serving HTTP requests. .. note:: By default, Jupyter Server runs locally at 127.0.0.1:8888 - and is accessible only from `localhost`. You may access the - server from the browser using `http://127.0.0.1:8888`. + and is accessible only from ``localhost``. You may access the + server from the browser using ``http://127.0.0.1:8888``. This document describes how you can :ref:`secure a Jupyter server ` and how to @@ -113,12 +113,12 @@ Preparing a hashed password You can prepare a hashed password manually, using the function :func:`jupyter_server.auth.passwd`: -.. code-block:: python +.. code-block:: pycon >>> from jupyter_server.auth import passwd >>> passwd() - ... Enter password: - ... Verify password: + Enter password: + Verify password: 'sha1:67c9e60bb8b6:9ffede0825894254b2e042ea597d771089e11aed' .. caution:: @@ -308,7 +308,7 @@ instructions about modifying ``jupyter_server_config.py``): .. code-block:: python - c.ServerApp.base_url = '/ipython/' + c.ServerApp.base_url = "/ipython/" Embedding the notebook in another website ----------------------------------------- @@ -316,15 +316,15 @@ Embedding the notebook in another website Sometimes you may want to embed the notebook somewhere on your website, e.g. in an IFrame. To do this, you may need to override the Content-Security-Policy to allow embedding. Assuming your website is at -`https://mywebsite.example.com`, you can embed the notebook on your website +``https://mywebsite.example.com``, you can embed the notebook on your website with the following configuration setting in :file:`jupyter_server_config.py`: .. code-block:: python c.ServerApp.tornado_settings = { - 'headers': { - 'Content-Security-Policy': "frame-ancestors https://mywebsite.example.com 'self' " + "headers": { + "Content-Security-Policy": "frame-ancestors https://mywebsite.example.com 'self' " } } @@ -351,7 +351,7 @@ or in :file:`jupyter_notebook_config.py`: .. code-block:: python - c.GatewayClient.url = 'http://my-gateway-server:8888' + c.GatewayClient.url = "http://my-gateway-server:8888" When provided, all kernel specifications will be retrieved from the specified Gateway server and all kernels will be managed by that server. This option enables the ability to target kernel processes @@ -417,7 +417,7 @@ Using ``jupyter server`` as a kernels repeatedly crashing, likely due to a lack of `PID reaping `_. To avoid this, use the `tini `_ ``init`` as your -Dockerfile `ENTRYPOINT`:: +Dockerfile ``ENTRYPOINT``:: # Add Tini. Tini operates as a process subreaper for jupyter. This prevents # kernel crashes. diff --git a/docs/source/operators/security.rst b/docs/source/operators/security.rst index 7242e441d0..2a63db7087 100644 --- a/docs/source/operators/security.rst +++ b/docs/source/operators/security.rst @@ -69,8 +69,8 @@ but this is **NOT RECOMMENDED**, unless authentication or access restrictions ar .. sourcecode:: python - c.ServerApp.token = '' - c.ServerApp.password = '' + c.ServerApp.token = "" + c.ServerApp.password = "" Authentication and Authorization @@ -114,11 +114,11 @@ It should return None if the request is not authenticated. The default implementation accepts token or password authentication. -This User object will be available as `self.current_user` in any request handler. -Request methods decorated with tornado's `@web.authenticated` decorator +This User object will be available as ``self.current_user`` in any request handler. +Request methods decorated with tornado's ``@web.authenticated`` decorator will only be allowed if this method returns something. -The User object will be a Python :py:class:`dataclasses.dataclass` - `jupyter_server.auth.User`: +The User object will be a Python :py:class:`dataclasses.dataclass` - ``jupyter_server.auth.User``: .. autoclass:: jupyter_server.auth.User @@ -126,28 +126,28 @@ A custom IdentityProvider *may* return a custom subclass. The next method an identity provider has is :meth:`~jupyter_server.auth.IdentityProvider.identity_model`. -`identity_model(user)` is responsible for transforming the user object returned from `.get_user()` +``identity_model(user)`` is responsible for transforming the user object returned from ``.get_user()`` into a standard identity model dictionary, -for use in the `/api/me` endpoint. +for use in the ``/api/me`` endpoint. -If your user object is a simple username string or a dict with a `username` field, +If your user object is a simple username string or a dict with a ``username`` field, you may not need to implement this method, as the default implementation will suffice. Any required fields missing from the dict returned by this method will be filled-out with defaults. -Only `username` is strictly required, if that is all the information the identity provider has available. +Only ``username`` is strictly required, if that is all the information the identity provider has available. Missing will be derived according to: -- if `name` is missing, use `username` -- if `display_name` is missing, use `name` +- if ``name`` is missing, use ``username`` +- if ``display_name`` is missing, use ``name`` -Other required fields will be filled with `None`. +Other required fields will be filled with ``None``. Identity Model ^^^^^^^^^^^^^^ -The identity model is the model accessed at `/api/me`, and describes the currently authenticated user. +The identity model is the model accessed at ``/api/me``, and describes the currently authenticated user. It has the following fields: @@ -158,25 +158,25 @@ username name (string) For-humans name of the user. - May be the same as `username` in systems where only usernames are available. + May be the same as ``username`` in systems where only usernames are available. display_name (string) Alternate rendering of name for display, such as a nickname. - Often the same as `name`. + Often the same as ``name``. initials (string or null) Short string of initials. Initials should not be derived automatically due to localization issues. - May be `null` if unavailable. + May be ``null`` if unavailable. avatar_url (string or null) URL of an avatar image to be used for the user. - May be `null` if unavailable. + May be ``null`` if unavailable. color (string or null) A CSS color string to use as a preferred color, such as for collaboration cursors. - May be `null` if unavailable. + May be ``null`` if unavailable. The default implementation of the identity provider is stateless, meaning it doesn't store user information @@ -222,7 +222,7 @@ request handler. Each request is labeled as either a "read", "write", or "execut to ~all other permissions via other means. The ``resource`` being accessed refers to the resource name in the Jupyter Server's API endpoints. -In most cases, this is the field after `/api/`. +In most cases, this is the field after ``/api/``. For instance, values for ``resource`` in the endpoints provided by the base Jupyter Server package, and the corresponding permissions: @@ -316,6 +316,7 @@ follows: from jupyter_server.auth import Authorizer + class MyAuthorizationManager(Authorizer): """Class for authorizing access to resources in the Jupyter Server. @@ -328,7 +329,9 @@ follows: is accepted; if it returns False, the server returns a 403 (Forbidden) error code. """ - def is_authorized(self, handler: JupyterHandler, user: Any, action: str, resource: str) -> bool: + def is_authorized( + self, handler: JupyterHandler, user: Any, action: str, resource: str + ) -> bool: """A method to determine if `user` is authorized to perform `action` (read, write, or execute) on the `resource` type. diff --git a/docs/source/users/configuration.rst b/docs/source/users/configuration.rst index 4deace162c..696cba7292 100644 --- a/docs/source/users/configuration.rst +++ b/docs/source/users/configuration.rst @@ -24,7 +24,7 @@ By default, Jupyter Server looks for server-specific configuration in a ``jupyte /Users/username/Library/Jupyter/runtime -The paths under ``config`` are listed in order of precedence. If the same trait is listed in multiple places, it will be set to the value from the file with the highest precendence. +The paths under ``config`` are listed in order of precedence. If the same trait is listed in multiple places, it will be set to the value from the file with the highest precedence. Jupyter Server uses IPython's traitlets system for configuration. Traits can be diff --git a/examples/authorization/README.md b/examples/authorization/README.md index 8bbbe22528..1d9d27814b 100644 --- a/examples/authorization/README.md +++ b/examples/authorization/README.md @@ -31,6 +31,7 @@ class MyCustomAuthorizer(Authorizer): return True + # Pass this custom class to Jupyter Server c.ServerApp.authorizer_class = MyCustomAuthorizer ``` diff --git a/examples/identity/system_password/jupyter_server_config.py b/examples/identity/system_password/jupyter_server_config.py index 355a6533fb..440158bfbc 100644 --- a/examples/identity/system_password/jupyter_server_config.py +++ b/examples/identity/system_password/jupyter_server_config.py @@ -2,7 +2,7 @@ import pwd from getpass import getuser -from pamela import PAMError, authenticate # type:ignore +from pamela import PAMError, authenticate # type:ignore[import] from jupyter_server.auth.identity import IdentityProvider, User @@ -28,6 +28,6 @@ def process_login_form(self, handler): return User(username=username, name=user_info.pw_gecos or username) -c = get_config() # type: ignore +c = get_config() # type: ignore[name-defined] c.ServerApp.identity_provider_class = SystemPasswordIdentityProvider diff --git a/examples/simple/README.md b/examples/simple/README.md index 49fe480252..dd76af4ded 100644 --- a/examples/simple/README.md +++ b/examples/simple/README.md @@ -137,7 +137,7 @@ The content of the Config is based on the trait you have defined via the `CLI` a ## Only Extension 2 -Now stop agin the server and start with only `Extension 2`. +Now stop again the server and start with only `Extension 2`. ```bash # Start the jupyter server extension simple_ext2, it will NOT load simple_ext1 because of load_other_extensions = False. diff --git a/examples/simple/conftest.py b/examples/simple/conftest.py index 891a4d4eb4..df81e57d34 100644 --- a/examples/simple/conftest.py +++ b/examples/simple/conftest.py @@ -1,2 +1,2 @@ -"""Pytest configurtion.""" +"""Pytest configuration.""" pytest_plugins = ["jupyter_server.pytest_plugin"] diff --git a/examples/simple/simple_ext11/application.py b/examples/simple/simple_ext11/application.py index 57c7c1d27f..34f441cf16 100644 --- a/examples/simple/simple_ext11/application.py +++ b/examples/simple/simple_ext11/application.py @@ -1,7 +1,7 @@ """A Jupyter Server example application.""" import os -from simple_ext1.application import SimpleApp1 # type:ignore +from simple_ext1.application import SimpleApp1 # type:ignore[import] from traitlets import Bool, Unicode, observe from jupyter_server.serverapp import aliases, flags @@ -23,7 +23,7 @@ class SimpleApp11(SimpleApp1): # The name of the extension. name = "simple_ext11" - # Te url that your extension will serve its homepage. + # The url that your extension will serve its homepage. extension_url = "/simple_ext11/default" # Local path to static files directory. diff --git a/examples/simple/simple_ext2/application.py b/examples/simple/simple_ext2/application.py index 7bf7803b14..b4fbbadb97 100644 --- a/examples/simple/simple_ext2/application.py +++ b/examples/simple/simple_ext2/application.py @@ -17,7 +17,7 @@ class SimpleApp2(ExtensionAppJinjaMixin, ExtensionApp): # The name of the extension. name = "simple_ext2" - # Te url that your extension will serve its homepage. + # The url that your extension will serve its homepage. extension_url = "/simple_ext2" # Should your extension expose other server extensions when launched directly? diff --git a/jupyter_server/_tz.py b/jupyter_server/_tz.py index e2d04434cf..4444c93db0 100644 --- a/jupyter_server/_tz.py +++ b/jupyter_server/_tz.py @@ -23,7 +23,7 @@ def dst(self, d): return ZERO -UTC = tzUTC() # type:ignore +UTC = tzUTC() # type:ignore[abstract] def utc_aware(unaware): diff --git a/jupyter_server/base/handlers.py b/jupyter_server/base/handlers.py index fc20ce251c..32eeea3656 100644 --- a/jupyter_server/base/handlers.py +++ b/jupyter_server/base/handlers.py @@ -579,7 +579,7 @@ def check_host(self): return allow async def prepare(self): - """Pepare a response.""" + """Prepare a response.""" # Set the current Jupyter Handler context variable. CallContext.set(CallContext.JUPYTER_HANDLER, self) diff --git a/jupyter_server/event_schemas/contents_service/v1.yaml b/jupyter_server/event_schemas/contents_service/v1.yaml index 2c574f7b93..a787f9b2b0 100644 --- a/jupyter_server/event_schemas/contents_service/v1.yaml +++ b/jupyter_server/event_schemas/contents_service/v1.yaml @@ -5,7 +5,7 @@ personal-data: true description: | Record actions on files via the ContentsManager. - The notebook ContentsManager REST API is used by all frontends to retreive, + The notebook ContentsManager REST API is used by all frontends to retrieve, save, list, delete and perform other actions on notebooks, directories, and other files through the UI. This is pluggable - the default acts on the file system, but can be replaced with a different ContentsManager diff --git a/jupyter_server/event_schemas/gateway_client/v1.yaml b/jupyter_server/event_schemas/gateway_client/v1.yaml index 4ddad94428..0a35d2464d 100644 --- a/jupyter_server/event_schemas/gateway_client/v1.yaml +++ b/jupyter_server/event_schemas/gateway_client/v1.yaml @@ -6,8 +6,8 @@ description: | Record events of a gateway client. type: object required: - - status - - msg + - status + - msg properties: status: enum: diff --git a/jupyter_server/event_schemas/kernel_actions/v1.yaml b/jupyter_server/event_schemas/kernel_actions/v1.yaml index b15cc70326..e0375e5aaa 100644 --- a/jupyter_server/event_schemas/kernel_actions/v1.yaml +++ b/jupyter_server/event_schemas/kernel_actions/v1.yaml @@ -69,12 +69,12 @@ properties: description: | Description of the event specified in action. if: - not: - properties: - status: - const: error - action: - const: start + not: + properties: + status: + const: error + action: + const: start then: required: - kernel_id diff --git a/jupyter_server/extension/application.py b/jupyter_server/extension/application.py index be34d6c2f2..984b2438cd 100644 --- a/jupyter_server/extension/application.py +++ b/jupyter_server/extension/application.py @@ -560,7 +560,7 @@ def make_serverapp(cls, **kwargs): @classmethod def initialize_server(cls, argv=None, load_other_extensions=True, **kwargs): """Creates an instance of ServerApp and explicitly sets - this extension to enabled=True (i.e. superceding disabling + this extension to enabled=True (i.e. superseding disabling found in other config from files). The `launch_instance` method uses this method to initialize diff --git a/jupyter_server/extension/utils.py b/jupyter_server/extension/utils.py index 6d3fcdbf2b..69af455b7c 100644 --- a/jupyter_server/extension/utils.py +++ b/jupyter_server/extension/utils.py @@ -63,7 +63,7 @@ def get_metadata(package_name, logger=None): This looks for a `_jupyter_server_extension_points` function that returns metadata about all extension points within a Jupyter - Server Extension pacakge. + Server Extension package. If it doesn't exist, return a basic metadata packet given the module name. diff --git a/jupyter_server/gateway/gateway_client.py b/jupyter_server/gateway/gateway_client.py index ebdaa41249..d73c0245aa 100644 --- a/jupyter_server/gateway/gateway_client.py +++ b/jupyter_server/gateway/gateway_client.py @@ -41,7 +41,7 @@ from http.cookies import Morsel -class GatewayTokenRenewerMeta(ABCMeta, type(LoggingConfigurable)): # type: ignore +class GatewayTokenRenewerMeta(ABCMeta, type(LoggingConfigurable)): # type: ignore[misc] """The metaclass necessary for proper ABC behavior in a Configurable.""" pass @@ -589,8 +589,8 @@ def init_connection_args(self): self._connection_args["auth_password"] = self.http_pwd def load_connection_args(self, **kwargs): - """Merges the static args relative to the connection, with the given keyword arguments. If statics - have yet to be initialized, we'll do that here. + """Merges the static args relative to the connection, with the given keyword arguments. If static + args have yet to be initialized, we'll do that here. """ if len(self._connection_args) == 0: diff --git a/jupyter_server/gateway/handlers.py b/jupyter_server/gateway/handlers.py index 52ec6ae474..7d532211ed 100644 --- a/jupyter_server/gateway/handlers.py +++ b/jupyter_server/gateway/handlers.py @@ -67,12 +67,12 @@ def authenticate(self): if self.get_argument("session_id", None): assert self.session is not None - self.session.session = self.get_argument("session_id") + self.session.session = self.get_argument("session_id") # type:ignore[unreachable] else: self.log.warning("No session ID specified") def initialize(self): - """Intialize the socket.""" + """Initialize the socket.""" self.log.debug("Initializing websocket connection %s", self.request.path) self.session = Session(config=self.config) self.gateway = GatewayWebSocketClient(gateway_url=GatewayClient.instance().url) @@ -87,7 +87,7 @@ async def get(self, kernel_id, *args, **kwargs): def send_ping(self): """Send a ping to the socket.""" if self.ws_connection is None and self.ping_callback is not None: - self.ping_callback.stop() + self.ping_callback.stop() # type:ignore[unreachable] return self.ping(b"") diff --git a/jupyter_server/gateway/managers.py b/jupyter_server/gateway/managers.py index f2d12d7b0b..919d28d854 100644 --- a/jupyter_server/gateway/managers.py +++ b/jupyter_server/gateway/managers.py @@ -11,7 +11,7 @@ from time import monotonic from typing import Any, Dict, Optional -import websocket # type:ignore +import websocket # type:ignore[import] from jupyter_client.asynchronous.client import AsyncKernelClient from jupyter_client.clientabc import KernelClientABC from jupyter_client.kernelspec import KernelSpecManager @@ -99,7 +99,7 @@ async def kernel_model(self, kernel_id): """ model = None km = self.get_kernel(str(kernel_id)) - if km: + if km: # type:ignore[truthy-bool] model = km.kernel # type:ignore[attr-defined] return model @@ -678,7 +678,7 @@ def is_alive(self) -> bool: class HBChannelQueue(ChannelQueue): - """A queue for the hearbeat channel.""" + """A queue for the heartbeat channel.""" def is_beating(self) -> bool: """Whether the channel is beating.""" diff --git a/jupyter_server/i18n/notebook.pot b/jupyter_server/i18n/notebook.pot index 333b40d76c..b8d588f964 100644 --- a/jupyter_server/i18n/notebook.pot +++ b/jupyter_server/i18n/notebook.pot @@ -280,7 +280,7 @@ msgid "server_extensions is deprecated, use jpserver_extensions" msgstr "" #: jupyter_server/serverapp.py:1040 -msgid "Dict of Python modules to load as notebook server extensions. Entry values can be used to enable and disable the loading ofthe extensions. The extensions will be loaded in alphabetical order." +msgid "Dict of Python modules to load as notebook server extensions. Entry values can be used to enable and disable the loading of the extensions. The extensions will be loaded in alphabetical order." msgstr "" #: jupyter_server/serverapp.py:1049 diff --git a/jupyter_server/i18n/zh_CN/LC_MESSAGES/notebook.po b/jupyter_server/i18n/zh_CN/LC_MESSAGES/notebook.po index ee74a2097c..8f65bd35bf 100644 --- a/jupyter_server/i18n/zh_CN/LC_MESSAGES/notebook.po +++ b/jupyter_server/i18n/zh_CN/LC_MESSAGES/notebook.po @@ -283,7 +283,7 @@ msgid "No such notebook dir: '%r'" msgstr "没有找到路径: '%r' " #: notebook/serverapp.py:1046 -msgid "Dict of Python modules to load as notebook server extensions.Entry values can be used to enable and disable the loading ofthe extensions. The extensions will be loaded in alphabetical order." +msgid "Dict of Python modules to load as notebook server extensions.Entry values can be used to enable and disable the loading of the extensions. The extensions will be loaded in alphabetical order." msgstr "将Python模块作为笔记本服务器扩展加载。可以使用条目值来启用和禁用扩展的加载。这些扩展将以字母顺序加载。" #: notebook/serverapp.py:1055 diff --git a/jupyter_server/kernelspecs/handlers.py b/jupyter_server/kernelspecs/handlers.py index 301973223a..68334d8e8c 100644 --- a/jupyter_server/kernelspecs/handlers.py +++ b/jupyter_server/kernelspecs/handlers.py @@ -60,7 +60,7 @@ async def get(self, kernel_name, path, include_body=True): @web.authenticated @authorized async def head(self, kernel_name, path): - """Get the head infor for a kernel resource.""" + """Get the head info for a kernel resource.""" return await ensure_async(self.get(kernel_name, path, include_body=False)) diff --git a/jupyter_server/log.py b/jupyter_server/log.py index 7bb922880b..52eadadea8 100644 --- a/jupyter_server/log.py +++ b/jupyter_server/log.py @@ -85,7 +85,7 @@ def log_request(handler): msg = "{status} {method} {uri} ({username}@{ip}) {request_time:.2f}ms" if status >= 400: # noqa: PLR2004 - # log bad referers + # log bad referrers ns["referer"] = _scrub_uri(request.headers.get("Referer", "None")) msg = msg + " referer={referer}" if status >= 500 and status != 502: # noqa: PLR2004 diff --git a/jupyter_server/prometheus/metrics.py b/jupyter_server/prometheus/metrics.py index 73a5a02040..ac90d10578 100644 --- a/jupyter_server/prometheus/metrics.py +++ b/jupyter_server/prometheus/metrics.py @@ -9,7 +9,7 @@ # Jupyter Notebook also defines these metrics. Re-defining them results in a ValueError. # Try to de-duplicate by using the ones in Notebook if available. # See https://github.com/jupyter/jupyter_server/issues/209 - from notebook.prometheus.metrics import ( # type:ignore + from notebook.prometheus.metrics import ( # type:ignore[import] HTTP_REQUEST_DURATION_SECONDS, KERNEL_CURRENTLY_RUNNING_TOTAL, TERMINAL_CURRENTLY_RUNNING_TOTAL, diff --git a/jupyter_server/serverapp.py b/jupyter_server/serverapp.py index 54f6bef485..f9b9917772 100644 --- a/jupyter_server/serverapp.py +++ b/jupyter_server/serverapp.py @@ -1108,7 +1108,7 @@ def _deprecated_token_access(self): def _default_min_open_files_limit(self): if resource is None: # Ignoring min_open_files_limit because the limit cannot be adjusted (for example, on Windows) - return None + return None # type:ignore[unreachable] soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE) @@ -1956,7 +1956,7 @@ def init_configurables(self): # non-default login handler ignored because also explicitly set identity provider self.log.warning( f"Ignoring deprecated config ServerApp.login_handler_class={self.login_handler_class}." - " Superceded by ServerApp.identity_provider_class={self.identity_provider_class}." + " Superseded by ServerApp.identity_provider_class={self.identity_provider_class}." ) self.identity_provider = self.identity_provider_class(**identity_provider_kwargs) @@ -2122,7 +2122,7 @@ def init_webapp(self): def init_resources(self): """initialize system resources""" if resource is None: - self.log.debug( + self.log.debug( # type:ignore[unreachable] "Ignoring min_open_files_limit because the limit cannot be adjusted (for example, on Windows)" ) return @@ -2207,7 +2207,11 @@ def connection_url(self): def init_signal(self): """Initialize signal handlers.""" - if not sys.platform.startswith("win") and sys.stdin and sys.stdin.isatty(): + if ( + not sys.platform.startswith("win") + and sys.stdin # type:ignore[truthy-bool] + and sys.stdin.isatty() + ): signal.signal(signal.SIGINT, self._handle_sigint) signal.signal(signal.SIGTERM, self._signal_stop) if hasattr(signal, "SIGUSR1"): @@ -2544,7 +2548,7 @@ def initialize( CLI arguments to parse. find_extensions : bool If True, find and load extensions listed in Jupyter config paths. If False, - only load extensions that are passed to ServerApp directy through + only load extensions that are passed to ServerApp directly through the `argv`, `config`, or `jpserver_extensions` arguments. new_httpserver : bool If True, a tornado HTTPServer instance will be created and configured for the Server Web diff --git a/jupyter_server/services/api/api.yaml b/jupyter_server/services/api/api.yaml index 2861cf1dfb..9e2c7162af 100644 --- a/jupyter_server/services/api/api.yaml +++ b/jupyter_server/services/api/api.yaml @@ -604,7 +604,7 @@ paths: - terminals responses: 200: - description: Succesfully created a new terminal + description: Successfully created a new terminal schema: $ref: "#/definitions/Terminal" 403: @@ -637,7 +637,7 @@ paths: - $ref: "#/parameters/terminal_id" responses: 204: - description: Succesfully deleted terminal session + description: Successfully deleted terminal session 403: description: Forbidden to access 404: diff --git a/jupyter_server/services/contents/fileio.py b/jupyter_server/services/contents/fileio.py index 2392074291..ae9d70fa35 100644 --- a/jupyter_server/services/contents/fileio.py +++ b/jupyter_server/services/contents/fileio.py @@ -187,7 +187,7 @@ class FileManagerMixin(Configurable): True, config=True, help="""By default notebooks are saved on disk on a temporary file and then if succefully written, it replaces the old ones. - This procedure, namely 'atomic_writing', causes some bugs on file system whitout operation order enforcement (like some networked fs). + This procedure, namely 'atomic_writing', causes some bugs on file system without operation order enforcement (like some networked fs). If set to False, the new notebook is written directly on the old one which could fail (eg: full filesystem or quota )""", ) @@ -203,7 +203,7 @@ def atomic_writing(self, os_path, *args, **kwargs): Depending on flag 'use_atomic_writing', the wrapper perform an actual atomic writing or simply writes the file (whatever an old exists or not)""" with self.perm_to_403(os_path): - kwargs["log"] = self.log # type:ignore + kwargs["log"] = self.log # type:ignore[attr-defined] if self.use_atomic_writing: with atomic_writing(os_path, *args, **kwargs) as f: yield f @@ -223,7 +223,7 @@ def perm_to_403(self, os_path=""): # but nobody should be doing that anyway. if not os_path: os_path = e.filename or "unknown file" - path = to_api_path(os_path, root=self.root_dir) # type:ignore + path = to_api_path(os_path, root=self.root_dir) # type:ignore[attr-defined] raise HTTPError(403, "Permission denied: %s" % path) from e else: raise @@ -233,7 +233,7 @@ def _copy(self, src, dest): like shutil.copy2, but log errors in copystat """ - copy2_safe(src, dest, log=self.log) # type:ignore + copy2_safe(src, dest, log=self.log) # type:ignore[attr-defined] def _get_os_path(self, path): """Given an API path, return its file system path. @@ -252,7 +252,7 @@ def _get_os_path(self, path): ------ 404: if path is outside root """ - root = os.path.abspath(self.root_dir) # type:ignore + root = os.path.abspath(self.root_dir) # type:ignore[attr-defined] # to_os_path is not safe if path starts with a drive, since os.path.join discards first part if os.path.splitdrive(path)[0]: raise HTTPError(404, "%s is not a relative API path" % path) @@ -359,7 +359,7 @@ async def _copy(self, src, dest): like shutil.copy2, but log errors in copystat """ - await async_copy2_safe(src, dest, log=self.log) # type:ignore + await async_copy2_safe(src, dest, log=self.log) # type:ignore[attr-defined] async def _read_notebook(self, os_path, as_version=4, capture_validation_error=None): """Read a notebook from an os path.""" diff --git a/jupyter_server/services/contents/filemanager.py b/jupyter_server/services/contents/filemanager.py index 56bcbd3871..4ecfa6e00f 100644 --- a/jupyter_server/services/contents/filemanager.py +++ b/jupyter_server/services/contents/filemanager.py @@ -677,7 +677,7 @@ def _get_dir_size(self, path="."): """ try: if platform.system() == "Darwin": - # retuns the size of the folder in KB + # returns the size of the folder in KB result = subprocess.run( ["du", "-sk", path], capture_output=True, check=True ).stdout.split() @@ -1138,7 +1138,7 @@ async def _get_dir_size(self, path: str = ".") -> str: """ try: if platform.system() == "Darwin": - # retuns the size of the folder in KB + # returns the size of the folder in KB result = subprocess.run( ["du", "-sk", path], capture_output=True, check=True ).stdout.split() diff --git a/jupyter_server/services/contents/manager.py b/jupyter_server/services/contents/manager.py index 131a29248e..9482d42884 100644 --- a/jupyter_server/services/contents/manager.py +++ b/jupyter_server/services/contents/manager.py @@ -466,7 +466,7 @@ def rename_file(self, old_path, new_path): """Rename a file or directory.""" raise NotImplementedError - # ContentsManager API part 2: methods that have useable default + # ContentsManager API part 2: methods that have usable default # implementations, but can be overridden in subclasses. def delete(self, path): @@ -868,7 +868,7 @@ async def rename_file(self, old_path, new_path): """Rename a file or directory.""" raise NotImplementedError - # ContentsManager API part 2: methods that have useable default + # ContentsManager API part 2: methods that have usable default # implementations, but can be overridden in subclasses. async def delete(self, path): diff --git a/jupyter_server/services/kernels/connection/channels.py b/jupyter_server/services/kernels/connection/channels.py index 9ee3b3cca3..c90964ed82 100644 --- a/jupyter_server/services/kernels/connection/channels.py +++ b/jupyter_server/services/kernels/connection/channels.py @@ -450,8 +450,8 @@ def handle_incoming_message(self, incoming_msg: str) -> None: "header": None, } else: - if isinstance(ws_msg, bytes): - msg = deserialize_binary_message(ws_msg) + if isinstance(ws_msg, bytes): # type:ignore[unreachable] + msg = deserialize_binary_message(ws_msg) # type:ignore[unreachable] else: msg = json.loads(ws_msg) msg_list = [] @@ -468,7 +468,7 @@ def handle_incoming_message(self, incoming_msg: str) -> None: if am: msg["header"] = self.get_part("header", msg["header"], msg_list) assert msg["header"] is not None - if msg["header"]["msg_type"] not in am: + if msg["header"]["msg_type"] not in am: # type:ignore[unreachable] self.log.warning( 'Received message of type "%s", which is not allowed. Ignoring.' % msg["header"]["msg_type"] diff --git a/jupyter_server/services/kernels/kernelmanager.py b/jupyter_server/services/kernels/kernelmanager.py index f45fdff193..ca57228753 100644 --- a/jupyter_server/services/kernels/kernelmanager.py +++ b/jupyter_server/services/kernels/kernelmanager.py @@ -64,7 +64,7 @@ def _default_kernel_manager_class(self): _kernel_connections = Dict() - _kernel_ports: DictType[str, t.List[int]] = Dict() # type: ignore + _kernel_ports: DictType[str, t.List[int]] = Dict() # type: ignore[assignment] _culler_callback = None @@ -708,21 +708,21 @@ def __init__(self, **kwargs): self.last_kernel_activity = utcnow() -def emit_kernel_action_event(success_msg: str = ""): # type: ignore +def emit_kernel_action_event(success_msg: str = "") -> t.Callable: """Decorate kernel action methods to begin emitting jupyter kernel action events. Parameters ---------- success_msg: str - A formattable string thats passed to the message field of + A formattable string that's passed to the message field of the emitted event when the action succeeds. You can include the kernel_id, kernel_name, or action in the message using a formatted string argument, e.g. "{kernel_id} succeeded to {action}." error_msg: str - A formattable string thats passed to the message field of + A formattable string that's passed to the message field of the emitted event when the action fails. You can include the kernel_id, kernel_name, or action in the message using a formatted string argument, @@ -733,7 +733,7 @@ def wrap_method(method): @wraps(method) async def wrapped_method(self, *args, **kwargs): """""" - # Get the method name from teh + # Get the method name from the action = method.__name__.replace("_kernel", "") # If the method succeeds, emit a success event. try: diff --git a/jupyter_server/services/sessions/sessionmanager.py b/jupyter_server/services/sessions/sessionmanager.py index d20395e731..d923b8b8bf 100644 --- a/jupyter_server/services/sessions/sessionmanager.py +++ b/jupyter_server/services/sessions/sessionmanager.py @@ -14,7 +14,7 @@ import sqlite3 except ImportError: # fallback on pysqlite2 if Python was build without sqlite - from pysqlite2 import dbapi2 as sqlite3 # type:ignore + from pysqlite2 import dbapi2 as sqlite3 # type:ignore[import,no-redef] from dataclasses import dataclass, fields @@ -81,7 +81,7 @@ def __eq__(self, other: object) -> bool: def update(self, other: "KernelSessionRecord") -> None: """Updates in-place a kernel from other (only accepts positive updates""" if not isinstance(other, KernelSessionRecord): - msg = "'other' must be an instance of KernelSessionRecord." + msg = "'other' must be an instance of KernelSessionRecord." # type:ignore[unreachable] raise TypeError(msg) if other.kernel_id and self.kernel_id and other.kernel_id != self.kernel_id: diff --git a/jupyter_server/utils.py b/jupyter_server/utils.py index dc710c8c28..f08d279872 100644 --- a/jupyter_server/utils.py +++ b/jupyter_server/utils.py @@ -20,7 +20,7 @@ ) from urllib.request import pathname2url # noqa: F401 -from _frozen_importlib_external import _NamespacePath # type:ignore +from _frozen_importlib_external import _NamespacePath # type:ignore[import] from jupyter_core.utils import ensure_async from packaging.version import Version from tornado.httpclient import AsyncHTTPClient, HTTPClient, HTTPRequest @@ -327,7 +327,7 @@ def is_namespace_package(namespace): def filefind(filename, path_dirs=None): """Find a file by looking through a sequence of paths. This iterates through a sequence of paths looking for a file and returns - the full, absolute path of the first occurence of the file. If no set of + the full, absolute path of the first occurrence of the file. If no set of path dirs is given, the filename is tested as is, after running through :func:`expandvars` and :func:`expanduser`. Thus a simple call:: diff --git a/package.json b/package.json index bcedcc4a4d..2e5fc7657d 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,8 @@ "*build*" ], "babel": { - "presets": ["es2015"] + "presets": [ + "es2015" + ] } } diff --git a/pyproject.toml b/pyproject.toml index a662c50b5a..5ba07d5fb4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -104,7 +104,7 @@ nowarn = "test -W default {args}" [tool.hatch.envs.typing] features = ["test"] -dependencies = [ "mypy>=0.990" ] +dependencies = [ "mypy>=1.5.1" ] [tool.hatch.envs.typing.scripts] test = "mypy --install-types --non-interactive {args:.}" @@ -261,7 +261,13 @@ unfixable = [ "tests/unix_sockets/test_serverapp_integration.py" = ["S603", "S607"] [tool.pytest.ini_options] -addopts = "-raXs --durations 10 --color=yes --doctest-modules" +minversion = "6.0" +xfail_strict = true +log_cli_level = "info" +addopts = [ + "-raXs", "--durations=10", "--color=yes", "--doctest-modules", + "--showlocals", "--strict-markers", "--strict-config" +] testpaths = [ "tests/" ] @@ -275,8 +281,8 @@ filterwarnings = [ "always:unclosed