Skip to content

Commit

Permalink
Add mypy check (jupyter-server#779)
Browse files Browse the repository at this point in the history
  • Loading branch information
blink1073 authored May 3, 2022
1 parent eff1b08 commit 3e64fa5
Show file tree
Hide file tree
Showing 52 changed files with 266 additions and 141 deletions.
7 changes: 7 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,13 @@ repos:
files: \.py$
args: [--profile=black]

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v0.942
hooks:
- id: mypy
exclude: examples/simple/setup.py
additional_dependencies: [types-requests]

- repo: https://github.com/pre-commit/mirrors-prettier
rev: v2.6.2
hooks:
Expand Down
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import shutil
import sys

from pkg_resources import parse_version
from packaging.version import parse as parse_version

HERE = osp.abspath(osp.dirname(__file__))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ def is_authorized(self, handler, user, action, resource):
return True


c.ServerApp.authorizer_class = ReadOnly
c.ServerApp.authorizer_class = ReadOnly # type:ignore[name-defined]
2 changes: 1 addition & 1 deletion examples/authorization/jupyter_nbclassic_rw_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ def is_authorized(self, handler, user, action, resource):
return True


c.ServerApp.authorizer_class = ReadWriteOnly
c.ServerApp.authorizer_class = ReadWriteOnly # type:ignore[name-defined]
2 changes: 1 addition & 1 deletion examples/authorization/jupyter_temporary_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,4 @@ def is_authorized(self, handler, user, action, resource):
return True


c.ServerApp.authorizer_class = TemporaryServerPersonality
c.ServerApp.authorizer_class = TemporaryServerPersonality # type:ignore[name-defined]
4 changes: 3 additions & 1 deletion examples/simple/jupyter_server_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,6 @@
# Application(SingletonConfigurable) configuration
# ------------------------------------------------------------------------------
# The date format used by logging formatters for %(asctime)s
c.Application.log_datefmt = "%Y-%m-%d %H:%M:%S Simple_Extensions_Example"
c.Application.log_datefmt = ( # type:ignore[name-defined]
"%Y-%m-%d %H:%M:%S Simple_Extensions_Example"
)
2 changes: 1 addition & 1 deletion examples/simple/jupyter_simple_ext11_config.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
c.SimpleApp11.ignore_js = True
c.SimpleApp11.ignore_js = True # type:ignore[name-defined]
8 changes: 4 additions & 4 deletions examples/simple/jupyter_simple_ext1_config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
c.SimpleApp1.configA = "ConfigA from file"
c.SimpleApp1.configB = "ConfigB from file"
c.SimpleApp1.configC = "ConfigC from file"
c.SimpleApp1.configD = "ConfigD from file"
c.SimpleApp1.configA = "ConfigA from file" # type:ignore[name-defined]
c.SimpleApp1.configB = "ConfigB from file" # type:ignore[name-defined]
c.SimpleApp1.configC = "ConfigC from file" # type:ignore[name-defined]
c.SimpleApp1.configD = "ConfigD from file" # type:ignore[name-defined]
2 changes: 1 addition & 1 deletion examples/simple/jupyter_simple_ext2_config.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
c.SimpleApp2.configD = "ConfigD from file"
c.SimpleApp2.configD = "ConfigD from file" # type:ignore[name-defined]
2 changes: 1 addition & 1 deletion jupyter_server/auth/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ def post(self):
elif self.token and self.token == typed_password:
self.set_login_cookie(self, uuid.uuid4().hex)
if new_password and self.settings.get("allow_password_change"):
config_dir = self.settings.get("config_dir")
config_dir = self.settings.get("config_dir", "")
config_file = os.path.join(config_dir, "jupyter_server_config.json")
set_password(new_password, config_file=config_file)
self.log.info("Wrote hashed password to %s" % config_file)
Expand Down
4 changes: 2 additions & 2 deletions jupyter_server/auth/security.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,9 @@ def passwd(passphrase=None, algorithm="argon2"):
time_cost=10,
parallelism=8,
)
h = ph.hash(passphrase)
h_ph = ph.hash(passphrase)

return ":".join((algorithm, h))
return ":".join((algorithm, h_ph))

h = hashlib.new(algorithm)
salt = ("%0" + str(salt_len) + "x") % random.getrandbits(4 * salt_len)
Expand Down
6 changes: 3 additions & 3 deletions jupyter_server/auth/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@ def get_regex_to_resource_map():
from jupyter_server.serverapp import JUPYTER_SERVICE_HANDLERS

modules = []
for mod in JUPYTER_SERVICE_HANDLERS.values():
if mod:
modules.extend(mod)
for mod_name in JUPYTER_SERVICE_HANDLERS.values():
if mod_name:
modules.extend(mod_name)
resource_map = {}
for handler_module in modules:
mod = importlib.import_module(handler_module)
Expand Down
29 changes: 18 additions & 11 deletions jupyter_server/base/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def force_clear_cookie(self, name, path="/", domain=None):
name = escape.native_str(name)
expires = datetime.datetime.utcnow() - datetime.timedelta(days=365)

morsel = Morsel()
morsel: Morsel = Morsel()
morsel.set(name, "", '""')
morsel["expires"] = httputil.format_timestamp(expires)
morsel["path"] = path
Expand Down Expand Up @@ -292,7 +292,7 @@ def mathjax_config(self):
return self.settings.get("mathjax_config", "TeX-AMS-MML_HTMLorMML-full,Safe")

@property
def base_url(self):
def base_url(self) -> str:
return self.settings.get("base_url", "/")

@property
Expand Down Expand Up @@ -537,7 +537,9 @@ def check_host(self):
return True

# Remove port (e.g. ':8888') from host
host = re.match(r"^(.*?)(:\d+)?$", self.request.host).group(1)
match = re.match(r"^(.*?)(:\d+)?$", self.request.host)
assert match is not None
host = match.group(1)

# Browsers format IPv6 addresses like [::1]; we need to remove the []
if host.startswith("[") and host.endswith("]"):
Expand Down Expand Up @@ -574,10 +576,10 @@ async def prepare(self):

from jupyter_server.auth import IdentityProvider

if (
type(self.identity_provider) is IdentityProvider
and inspect.getmodule(self.get_current_user).__name__ != __name__
):
mod_obj = inspect.getmodule(self.get_current_user)
assert mod_obj is not None

if type(self.identity_provider) is IdentityProvider and mod_obj.__name__ != __name__:
# check for overridden get_current_user + default IdentityProvider
# deprecated way to override auth (e.g. JupyterHub < 3.0)
# allow deprecated, overridden get_current_user
Expand Down Expand Up @@ -659,7 +661,7 @@ def write_error(self, status_code, **kwargs):
exc_info = kwargs.get("exc_info")
message = ""
status_message = responses.get(status_code, "Unknown HTTP Error")
exception = "(unknown)"

if exc_info:
exception = exc_info[1]
# get the custom message, if defined
Expand All @@ -672,6 +674,8 @@ def write_error(self, status_code, **kwargs):
reason = getattr(exception, "reason", "")
if reason:
status_message = reason
else:
exception = "(unknown)"

# build template namespace
ns = dict(
Expand Down Expand Up @@ -703,7 +707,7 @@ def write_error(self, status_code, **kwargs):
"""APIHandler errors are JSON, not human pages"""
self.set_header("Content-Type", "application/json")
message = responses.get(status_code, "Unknown HTTP Error")
reply = {
reply: dict = {
"message": message,
}
exc_info = kwargs.get("exc_info")
Expand Down Expand Up @@ -817,13 +821,14 @@ def head(self, path):

@web.authenticated
def get(self, path):
if os.path.splitext(path)[1] == ".ipynb" or self.get_argument("download", False):
if os.path.splitext(path)[1] == ".ipynb" or self.get_argument("download", None):
name = path.rsplit("/", 1)[-1]
self.set_attachment_header(name)

return web.StaticFileHandler.get(self, path)

def get_content_type(self):
assert self.absolute_path is not None
path = self.absolute_path.strip("/")
if "/" in path:
_, name = path.rsplit("/", 1)
Expand Down Expand Up @@ -902,7 +907,8 @@ class FileFindHandler(JupyterHandler, web.StaticFileHandler):
"""subclass of StaticFileHandler for serving files from a search path"""

# cache search results, don't search for files more than once
_static_paths = {}
_static_paths: dict = {}
root: tuple

def set_headers(self):
super().set_headers()
Expand Down Expand Up @@ -966,6 +972,7 @@ class TrailingSlashHandler(web.RequestHandler):
"""

def get(self):
assert self.request.uri is not None
path, *rest = self.request.uri.partition("?")
# trim trailing *and* leading /
# to avoid misinterpreting repeated '//'
Expand Down
23 changes: 15 additions & 8 deletions jupyter_server/base/zmqhandlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import re
import struct
import sys
from typing import Optional, no_type_check
from urllib.parse import urlparse

import tornado
Expand All @@ -17,6 +18,7 @@
from jupyter_client.jsonutil import extract_dates
from jupyter_client.session import Session
from tornado import ioloop, web
from tornado.iostream import IOStream
from tornado.websocket import WebSocketHandler

from .handlers import JupyterHandler
Expand Down Expand Up @@ -91,7 +93,7 @@ def serialize_msg_to_ws_v1(msg_or_list, channel, pack=None):
else:
msg_list = msg_or_list
channel = channel.encode("utf-8")
offsets = []
offsets: list = []
offsets.append(8 * (1 + 1 + len(msg_list) + 1))
offsets.append(len(channel) + offsets[-1])
for msg in msg_list:
Expand Down Expand Up @@ -120,27 +122,30 @@ class WebSocketMixin:
"""Mixin for common websocket options"""

ping_callback = None
last_ping = 0
last_pong = 0
stream = None
last_ping = 0.0
last_pong = 0.0
stream = None # type: Optional[IOStream]

@property
def ping_interval(self):
"""The interval for websocket keep-alive pings.
Set ws_ping_interval = 0 to disable pings.
"""
return self.settings.get("ws_ping_interval", WS_PING_INTERVAL)
return self.settings.get("ws_ping_interval", WS_PING_INTERVAL) # type:ignore[attr-defined]

@property
def ping_timeout(self):
"""If no ping is received in this many milliseconds,
close the websocket connection (VPNs, etc. can fail to cleanly close ws connections).
Default is max of 3 pings or 30 seconds.
"""
return self.settings.get("ws_ping_timeout", max(3 * self.ping_interval, WS_PING_INTERVAL))
return self.settings.get( # type:ignore[attr-defined]
"ws_ping_timeout", max(3 * self.ping_interval, WS_PING_INTERVAL)
)

def check_origin(self, origin=None):
@no_type_check
def check_origin(self, origin: Optional[str] = None) -> bool:
"""Check Origin == Host or Access-Control-Allow-Origin.
Tornado >= 4 calls this method automatically, raising 403 if it returns False.
Expand Down Expand Up @@ -186,6 +191,7 @@ def clear_cookie(self, *args, **kwargs):
"""meaningless for websockets"""
pass

@no_type_check
def open(self, *args, **kwargs):
self.log.debug("Opening websocket %s", self.request.path)

Expand All @@ -201,6 +207,7 @@ def open(self, *args, **kwargs):
self.ping_callback.start()
return super().open(*args, **kwargs)

@no_type_check
def send_ping(self):
"""send a ping to keep the websocket alive"""
if self.ws_connection is None and self.ping_callback is not None:
Expand Down Expand Up @@ -322,7 +329,7 @@ def pre_get(self):
if not self.authorizer.is_authorized(self, user, "execute", "kernels"):
raise web.HTTPError(403)

if self.get_argument("session_id", False):
if self.get_argument("session_id", None):
self.session.session = self.get_argument("session_id")
else:
self.log.warning("No session ID specified")
Expand Down
2 changes: 1 addition & 1 deletion jupyter_server/config_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ def get(self, section_name, include_root=True):
section_name,
"\n\t".join(paths),
)
data = {}
data: dict = {}
for path in paths:
if os.path.isfile(path):
with open(path, encoding="utf-8") as f:
Expand Down
6 changes: 3 additions & 3 deletions jupyter_server/extension/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ class method. This method can be set as a entry_point in
# A useful class property that subclasses can override to
# configure the underlying Jupyter Server when this extension
# is launched directly (using its `launch_instance` method).
serverapp_config = {}
serverapp_config: dict = {}

# Some subclasses will likely override this trait to flip
# the default value to False if they don't offer a browser
Expand Down Expand Up @@ -165,7 +165,7 @@ def config_file_paths(self):
# file, jupyter_{name}_config.
# This should also match the jupyter subcommand used to launch
# this extension from the CLI, e.g. `jupyter {name}`.
name = None
name = "ExtensionApp"

@classmethod
def get_extension_package(cls):
Expand Down Expand Up @@ -318,7 +318,7 @@ def _prepare_handlers(self):
handler = handler_items[1]

# Get handler kwargs, if given
kwargs = {}
kwargs: dict = {}
if issubclass(handler, ExtensionHandlerMixin):
kwargs["name"] = self.name

Expand Down
Loading

0 comments on commit 3e64fa5

Please sign in to comment.