Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add mypy check #779

Merged
merged 3 commits into from May 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions .pre-commit-config.yaml
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
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
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
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
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
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
@@ -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
@@ -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
@@ -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
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
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
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
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
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
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
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