Repository: jupyter-server/jupyter_server

Issue:
2.0.1: documentation uses `pkg_resources`
Despite move to `hatchling` as pep517 backend looks like documentation still needs `pkg_resources` whicj is part of the `setuptools`.
```console
+ /usr/bin/sphinx-build -n -T -b man docs/source build/sphinx/man
Running Sphinx v5.3.0
Initializing GitHub plugin

Traceback (most recent call last):
  File "/usr/lib/python3.8/site-packages/sphinx/registry.py", line 459, in load_extension
    mod = import_module(extname)
  File "/usr/lib64/python3.8/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1014, in _gcd_import
  File "<frozen importlib._bootstrap>", line 991, in _find_and_load
  File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 671, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 843, in exec_module
  File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
  File "/usr/lib/python3.8/site-packages/sphinxcontrib/openapi/__init__.py", line 12, in <module>
    from pkg_resources import get_distribution, DistributionNotFound
ModuleNotFoundError: No module named 'pkg_resources'

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/usr/lib/python3.8/site-packages/sphinx/cmd/build.py", line 276, in build_main
    app = Sphinx(args.sourcedir, args.confdir, args.outputdir,
  File "/usr/lib/python3.8/site-packages/sphinx/application.py", line 223, in __init__
    self.setup_extension(extension)
  File "/usr/lib/python3.8/site-packages/sphinx/application.py", line 398, in setup_extension
    self.registry.load_extension(self, extname)
  File "/usr/lib/python3.8/site-packages/sphinx/registry.py", line 462, in load_extension
    raise ExtensionError(__('Could not import extension %s') % extname,
sphinx.errors.ExtensionError: Could not import extension sphinxcontrib.openapi (exception: No module named 'pkg_resources')

Extension error:
Could not import extension sphinxcontrib.openapi (exception: No module named 'pkg_resources')
```
As `pkg_resources` already is marked as deprecated it would be good to switch to `importlib-metadata` module.


Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/jupyter-server/jupyter_server/pull/1168?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server) Report
Base: **79.93**% // Head: **39.30**% // Decreases project coverage by **`-40.63%`** :warning:
> Coverage data is based on head [(`383a4e2`)](https://codecov.io/gh/jupyter-server/jupyter_server/pull/1168?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server) compared to base [(`a6dea1b`)](https://codecov.io/gh/jupyter-server/jupyter_server/commit/a6dea1b0145d76e99521d387c6415e817d9e0a36?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server).
> Patch coverage: 66.66% of modified lines in pull request are covered.

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff             @@
##             main    #1168       +/-   ##
===========================================
- Coverage   79.93%   39.30%   -40.64%     
===========================================
  Files          68       68               
  Lines        8124     8124               
  Branches     1601     1601               
===========================================
- Hits         6494     3193     -3301     
- Misses       1205     4732     +3527     
+ Partials      425      199      -226     
```


| [Impacted Files](https://codecov.io/gh/jupyter-server/jupyter_server/pull/1168?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server) | Coverage Δ | |
|---|---|---|
| [jupyter\_server/services/sessions/sessionmanager.py](https://codecov.io/gh/jupyter-server/jupyter_server/pull/1168?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server#diff-anVweXRlcl9zZXJ2ZXIvc2VydmljZXMvc2Vzc2lvbnMvc2Vzc2lvbm1hbmFnZXIucHk=) | `26.22% <0.00%> (-61.78%)` | :arrow_down: |
| [jupyter\_server/gateway/managers.py](https://codecov.io/gh/jupyter-server/jupyter_server/pull/1168?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server#diff-anVweXRlcl9zZXJ2ZXIvZ2F0ZXdheS9tYW5hZ2Vycy5weQ==) | `25.76% <100.00%> (-57.62%)` | :arrow_down: |
| [jupyter\_server/utils.py](https://codecov.io/gh/jupyter-server/jupyter_server/pull/1168?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server#diff-anVweXRlcl9zZXJ2ZXIvdXRpbHMucHk=) | `54.11% <100.00%> (-31.18%)` | :arrow_down: |
| [...pyter\_server/services/contents/largefilemanager.py](https://codecov.io/gh/jupyter-server/jupyter_server/pull/1168?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server#diff-anVweXRlcl9zZXJ2ZXIvc2VydmljZXMvY29udGVudHMvbGFyZ2VmaWxlbWFuYWdlci5weQ==) | `11.11% <0.00%> (-72.73%)` | :arrow_down: |
| [jupyter\_server/services/contents/fileio.py](https://codecov.io/gh/jupyter-server/jupyter_server/pull/1168?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server#diff-anVweXRlcl9zZXJ2ZXIvc2VydmljZXMvY29udGVudHMvZmlsZWlvLnB5) | `21.87% <0.00%> (-68.23%)` | :arrow_down: |
| [...upyter\_server/services/contents/filecheckpoints.py](https://codecov.io/gh/jupyter-server/jupyter_server/pull/1168?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server#diff-anVweXRlcl9zZXJ2ZXIvc2VydmljZXMvY29udGVudHMvZmlsZWNoZWNrcG9pbnRzLnB5) | `22.35% <0.00%> (-67.65%)` | :arrow_down: |
| [jupyter\_server/gateway/handlers.py](https://codecov.io/gh/jupyter-server/jupyter_server/pull/1168?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server#diff-anVweXRlcl9zZXJ2ZXIvZ2F0ZXdheS9oYW5kbGVycy5weQ==) | `0.00% <0.00%> (-62.28%)` | :arrow_down: |
| [jupyter\_server/auth/utils.py](https://codecov.io/gh/jupyter-server/jupyter_server/pull/1168?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server#diff-anVweXRlcl9zZXJ2ZXIvYXV0aC91dGlscy5weQ==) | `33.33% <0.00%> (-60.61%)` | :arrow_down: |
| [jupyter\_server/services/contents/filemanager.py](https://codecov.io/gh/jupyter-server/jupyter_server/pull/1168?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server#diff-anVweXRlcl9zZXJ2ZXIvc2VydmljZXMvY29udGVudHMvZmlsZW1hbmFnZXIucHk=) | `15.42% <0.00%> (-59.86%)` | :arrow_down: |
| ... and [42 more](https://codecov.io/gh/jupyter-server/jupyter_server/pull/1168?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server) | |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server)

</details>

[:umbrella: View full report at Codecov](https://codecov.io/gh/jupyter-server/jupyter_server/pull/1168?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server).   
:loudspeaker: Do you have feedback about the report comment? [Let us know in this issue](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=jupyter-server).

vidartf: Some type errors on your main merge :)
blink1073: Yeah, it looks like our `terminals` shim is referencing things that do not exist in `jupyter_server_terminals`, I need to sort this out.

Patch:
diff --git a/docs/Makefile b/docs/Makefile
index 1765c64af..f760bf014 100644
--- a/docs/Makefile
+++ b/docs/Makefile
@@ -53,15 +53,11 @@ clean:
 	rm -rf $(BUILDDIR)/*
 	rm -rf source/config.rst
 
-html: source/config.rst
+html:
 	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
 	@echo
 	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
 
-source/config.rst:
-	python3 autogen_config.py
-	@echo "Created docs for config options"
-
 dirhtml:
 	$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
 	@echo
diff --git a/docs/autogen_config.py b/docs/autogen_config.py
deleted file mode 100644
index 5f7d4b9b3..000000000
--- a/docs/autogen_config.py
+++ /dev/null
@@ -1,45 +0,0 @@
-#!/usr/bin/env python
-import os
-
-from jupyter_server.serverapp import ServerApp
-
-header = """\
-.. _other-full-config:
-
-
-Config file and command line options
-====================================
-
-The Jupyter Server can be run with a variety of command line arguments.
-A list of available options can be found below in the :ref:`options section
-<options>`.
-
-Defaults for these options can also be set by creating a file named
-``jupyter_server_config.py`` in your Jupyter folder. The Jupyter
-folder is in your home directory, ``~/.jupyter``.
-
-To create a ``jupyter_server_config.py`` file, with all the defaults
-commented out, you can use the following command line::
-
-  $ jupyter server --generate-config
-
-
-.. _options:
-
-Options
--------
-
-This list of options can be generated by running the following and hitting
-enter::
-
-  $ jupyter server --help-all
-
-"""
-try:
-    destination = os.path.join(os.path.dirname(__file__), "source/other/full-config.rst")
-except BaseException:
-    destination = os.path.join(os.getcwd(), "other/full-config.rst")
-
-with open(destination, "w") as f:
-    f.write(header)
-    f.write(ServerApp().document_config_options())
diff --git a/docs/source/conf.py b/docs/source/conf.py
index e1eb6e946..baff0ac99 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -11,56 +11,14 @@
 #
 # All configuration values have a default; values that are commented out
 # serve to show the default.
-import logging
 import os
 import os.path as osp
 import shutil
 import sys
-from importlib.metadata import version
-
-from packaging.version import parse as parse_version
 
 HERE = osp.abspath(osp.dirname(__file__))
-
-
-# If extensions (or modules to document with autodoc) are in another directory,
-# add these directories to sys.path here. If the directory is relative to the
-# documentation root, use os.path.abspath to make it absolute, like shown here.
-
-# DEBUG for RTD
-log = logging.getLogger(__name__)
-info = log.info
-info("DEBUG:: sys.path")
-info("================")
-for item in sys.path:
-    info(item)
-
-# add repo root to sys.path
-# here = root/docs/source
-here = os.path.abspath(os.path.dirname(__file__))
-repo_root = os.path.dirname(os.path.dirname(here))
-sys.path.insert(0, repo_root)
-
-info("repo_root")
-info("=====================")
-info(repo_root)
-
-# DEBUG for post insert on RTD
-info("DEBUG:: Post insert to sys.path")
-info("===============================")
-for item in sys.path:
-    info(item)
-
-# Check if docs are being built by ReadTheDocs
-# If so, generate a config.rst file and populate it with documentation about
-# configuration options
-
-if os.environ.get("READTHEDOCS", ""):
-
-    # Readthedocs doesn't run our Makefile, so we do this to force it to generate
-    # the config docs.
-    with open("../autogen_config.py") as f:
-        exec(compile(f.read(), "../autogen_config.py", "exec"), {})  # noqa
+sys.path.insert(0, osp.join(HERE, "..", ""))
+from jupyter_server._version import version_info
 
 # -- General configuration ------------------------------------------------
 
@@ -119,10 +77,8 @@ github_project_url = "https://github.com/jupyter/jupyter_server"
 # |version| and |release|, also used in various other places throughout the
 # built documents.
 #
-__version__ = version("jupyter_server")
 # The short X.Y version.
-version_parsed = parse_version(__version__)
-version = f"{version_parsed.major}.{version_parsed.minor}"  # type:ignore
+version = f"{version_info[0]}.{version_info[1]}"
 
 # The language for content autogenerated by Sphinx. Refer to documentation
 # for a list of supported languages.
@@ -382,7 +338,48 @@ spelling_word_list_filename = "spelling_wordlist.txt"
 # import before any doc is built, so _ is guaranteed to be injected
 import jupyter_server.transutils  # noqa: F401
 
+CONFIG_HEADER = """\
+.. _other-full-config:
+
+
+Config file and command line options
+====================================
+
+The Jupyter Server can be run with a variety of command line arguments.
+A list of available options can be found below in the :ref:`options section
+<options>`.
+
+Defaults for these options can also be set by creating a file named
+``jupyter_server_config.py`` in your Jupyter folder. The Jupyter
+folder is in your home directory, ``~/.jupyter``.
+
+To create a ``jupyter_server_config.py`` file, with all the defaults
+commented out, you can use the following command line::
+
+  $ jupyter server --generate-config
+
+
+.. _options:
+
+Options
+-------
+
+This list of options can be generated by running the following and hitting
+enter::
+
+  $ jupyter server --help-all
+
+"""
+
 
 def setup(app):
     dest = osp.join(HERE, "other", "changelog.md")
     shutil.copy(osp.join(HERE, "..", "..", "CHANGELOG.md"), dest)
+
+    # Generate full-config docs.
+    from jupyter_server.serverapp import ServerApp
+
+    destination = os.path.join(HERE, "other/full-config.rst")
+    with open(destination, "w") as f:
+        f.write(CONFIG_HEADER)
+        f.write(ServerApp().document_config_options())
diff --git a/examples/identity/system_password/jupyter_server_config.py b/examples/identity/system_password/jupyter_server_config.py
index 0437241ea..b4071848a 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
+from pamela import PAMError, authenticate  # type:ignore
 
 from jupyter_server.auth.identity import IdentityProvider, User
 
diff --git a/jupyter_server/gateway/managers.py b/jupyter_server/gateway/managers.py
index a186cd7a4..7b19e874e 100644
--- a/jupyter_server/gateway/managers.py
+++ b/jupyter_server/gateway/managers.py
@@ -11,7 +11,7 @@ from threading import Thread
 from time import monotonic
 from typing import Any, Dict, Optional
 
-import websocket
+import websocket  # type:ignore
 from jupyter_client.asynchronous.client import AsyncKernelClient
 from jupyter_client.clientabc import KernelClientABC
 from jupyter_client.kernelspec import KernelSpecManager
diff --git a/jupyter_server/services/events/handlers.py b/jupyter_server/services/events/handlers.py
index 8bd533b55..86d2f3ada 100644
--- a/jupyter_server/services/events/handlers.py
+++ b/jupyter_server/services/events/handlers.py
@@ -55,11 +55,11 @@ class SubscribeWebsocket(
         """Routes events that are emitted by Jupyter Server's
         EventBus to a WebSocket client in the browser.
         """
-        self.event_logger.add_listener(listener=self.event_listener)
+        self.event_logger.add_listener(listener=self.event_listener)  # type:ignore[arg-type]
 
     def on_close(self):
         """Handle a socket close."""
-        self.event_logger.remove_listener(listener=self.event_listener)
+        self.event_logger.remove_listener(listener=self.event_listener)  # type:ignore[arg-type]
 
 
 def validate_model(data: Dict[str, Any]) -> None:
diff --git a/jupyter_server/services/sessions/sessionmanager.py b/jupyter_server/services/sessions/sessionmanager.py
index 2b64434a8..708713117 100644
--- a/jupyter_server/services/sessions/sessionmanager.py
+++ b/jupyter_server/services/sessions/sessionmanager.py
@@ -14,7 +14,7 @@ try:
     import sqlite3
 except ImportError:
     # fallback on pysqlite2 if Python was build without sqlite
-    from pysqlite2 import dbapi2 as sqlite3  # type:ignore[no-redef]
+    from pysqlite2 import dbapi2 as sqlite3  # type:ignore
 
 from dataclasses import dataclass, fields
 
diff --git a/jupyter_server/terminal/__init__.py b/jupyter_server/terminal/__init__.py
index 2e8157f0f..641bad66b 100644
--- a/jupyter_server/terminal/__init__.py
+++ b/jupyter_server/terminal/__init__.py
@@ -2,7 +2,7 @@
 import warnings
 
 # Shims
-from jupyter_server_terminals import api_handlers, initialize  # noqa
+from jupyter_server_terminals import api_handlers  # noqa
 from jupyter_server_terminals.handlers import TermSocket  # noqa
 from jupyter_server_terminals.terminalmanager import TerminalManager  # noqa
 
@@ -11,3 +11,7 @@ warnings.warn(
     DeprecationWarning,
     stacklevel=2,
 )
+
+
+def initialize(webapp, root_dir, connection_url, settings):
+    """Included for backward compat, but no-op."""
diff --git a/jupyter_server/terminal/api_handlers.py b/jupyter_server/terminal/api_handlers.py
index f6c2b006b..c20f2d8be 100644
--- a/jupyter_server/terminal/api_handlers.py
+++ b/jupyter_server/terminal/api_handlers.py
@@ -1,2 +1,6 @@
 """Terminal API handlers."""
-from jupyter_server_terminals import TerminalAPIHandler, TerminalHandler, TerminalRootHandler
+from jupyter_server_terminals.api_handlers import (
+    TerminalAPIHandler,
+    TerminalHandler,
+    TerminalRootHandler,
+)
diff --git a/jupyter_server/terminal/terminalmanager.py b/jupyter_server/terminal/terminalmanager.py
index 5a91c0836..d2bef723f 100644
--- a/jupyter_server/terminal/terminalmanager.py
+++ b/jupyter_server/terminal/terminalmanager.py
@@ -4,4 +4,4 @@
 """
 # Copyright (c) Jupyter Development Team.
 # Distributed under the terms of the Modified BSD License.
-from jupyter_server_terminals import TerminalManager  # noqa
+from jupyter_server_terminals.terminalmanager import TerminalManager  # noqa
diff --git a/jupyter_server/utils.py b/jupyter_server/utils.py
index 7e1ba2f2a..e9e9c6378 100644
--- a/jupyter_server/utils.py
+++ b/jupyter_server/utils.py
@@ -20,7 +20,7 @@ from urllib.parse import (
 )
 from urllib.request import pathname2url  # noqa: F401
 
-from _frozen_importlib_external import _NamespacePath
+from _frozen_importlib_external import _NamespacePath  # type:ignore
 from jupyter_core.utils import ensure_async
 from packaging.version import Version
 from tornado.httpclient import AsyncHTTPClient, HTTPClient, HTTPRequest
diff --git a/pyproject.toml b/pyproject.toml
index ae1922c3f..9c31c85ef 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -229,7 +229,8 @@ filterwarnings = [
   "ignore:run_pre_save_hook is deprecated:DeprecationWarning",
   "always:unclosed <socket.socket:ResourceWarning",
   "module:Jupyter is migrating its paths to use standard platformdirs:DeprecationWarning",
-  "ignore:jupyter_server.base.zmqhandlers module is deprecated in Jupyter Server 2.0:DeprecationWarning"
+  "ignore:jupyter_server.base.zmqhandlers module is deprecated in Jupyter Server 2.0:DeprecationWarning",
+  "ignore:zmq.eventloop.ioloop is deprecated in pyzmq 17:DeprecationWarning",
 ]
 
 [tool.coverage.report]
@@ -268,18 +269,6 @@ warn_redundant_casts = true
 explicit_package_bases = true
 namespace_packages = true
 
-[[tool.mypy.overrides]]
-module = [
-    "pamela",
-    "pysqlite2",
-    "jupyter_events.*",
-    "jupyter_server_terminals.*",
-    "_frozen_importlib_external",
-    "send2trash",
-    "websocket"
-]
-ignore_missing_imports = true
-
 [tool.interrogate]
 ignore-init-module=true
 ignore-private=true


Test patch:
diff --git a/tests/test_terminal.py b/tests/test_terminal.py
index 7591798ae..3af0e5779 100644
--- a/tests/test_terminal.py
+++ b/tests/test_terminal.py
@@ -4,6 +4,7 @@ import os
 import shlex
 import shutil
 import sys
+import warnings
 
 import pytest
 from tornado.httpclient import HTTPClientError
@@ -291,3 +292,12 @@ def test_shell_command_override(
         )
     else:
         assert app.web_app.settings["terminal_manager"].shell_command == expected_shell
+
+
+def test_importing_shims():
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore")
+        from jupyter_server.terminal import initialize  # noqa
+        from jupyter_server.terminal.api_handlers import TerminalRootHandler  # noqa
+        from jupyter_server.terminal.handlers import TermSocket  # noqa
+        from jupyter_server.terminal.terminalmanager import TerminalManager  # noqa