Skip to content

Commit 9f8ff28

Browse files
authored
Add more typings (#1356)
1 parent a20fe64 commit 9f8ff28

26 files changed

+212
-192
lines changed

jupyter_server/_tz.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
ZERO = timedelta(0)
1414

1515

16-
class tzUTC(tzinfo): # noqa
16+
class tzUTC(tzinfo): # noqa: N801
1717
"""tzinfo object for UTC (zero offset)"""
1818

1919
def utcoffset(self, d: datetime | None) -> timedelta:
@@ -30,7 +30,7 @@ def utcnow() -> datetime:
3030
return datetime.now(timezone.utc)
3131

3232

33-
def utcfromtimestamp(timestamp):
33+
def utcfromtimestamp(timestamp: float) -> datetime:
3434
return datetime.fromtimestamp(timestamp, timezone.utc)
3535

3636

jupyter_server/auth/decorator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,6 @@ def inner(self, *args, **kwargs):
7373
method = action
7474
action = None
7575
# no-arguments `@authorized` decorator called
76-
return wrapper(method)
76+
return cast(FuncT, wrapper(method))
7777

7878
return cast(FuncT, wrapper)

jupyter_server/auth/identity.py

Lines changed: 52 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313
import os
1414
import re
1515
import sys
16+
import typing as t
1617
import uuid
1718
from dataclasses import asdict, dataclass
1819
from http.cookies import Morsel
19-
from typing import TYPE_CHECKING, Any, Awaitable
2020

2121
from tornado import escape, httputil, web
2222
from traitlets import Bool, Dict, Type, Unicode, default
@@ -27,11 +27,6 @@
2727
from .security import passwd_check, set_password
2828
from .utils import get_anonymous_username
2929

30-
# circular imports for type checking
31-
if TYPE_CHECKING:
32-
from jupyter_server.base.handlers import AuthenticatedHandler, JupyterHandler
33-
from jupyter_server.serverapp import ServerApp
34-
3530
_non_alphanum = re.compile(r"[^A-Za-z0-9]")
3631

3732

@@ -82,7 +77,7 @@ def fill_defaults(self):
8277
self.display_name = self.name
8378

8479

85-
def _backward_compat_user(got_user: Any) -> User:
80+
def _backward_compat_user(got_user: t.Any) -> User:
8681
"""Backward-compatibility for LoginHandler.get_user
8782
8883
Prior to 2.0, LoginHandler.get_user could return anything truthy.
@@ -128,7 +123,7 @@ class IdentityProvider(LoggingConfigurable):
128123
.. versionadded:: 2.0
129124
"""
130125

131-
cookie_name: str | Unicode = Unicode(
126+
cookie_name: str | Unicode[str, str | bytes] = Unicode(
132127
"",
133128
config=True,
134129
help=_i18n("Name of the cookie to set for persisting login. Default: username-${Host}."),
@@ -142,7 +137,7 @@ class IdentityProvider(LoggingConfigurable):
142137
),
143138
)
144139

145-
secure_cookie: bool | Bool = Bool(
140+
secure_cookie: bool | Bool[bool | None, bool | int | None] = Bool(
146141
None,
147142
allow_none=True,
148143
config=True,
@@ -160,7 +155,7 @@ class IdentityProvider(LoggingConfigurable):
160155
),
161156
)
162157

163-
token: str | Unicode = Unicode(
158+
token: str | Unicode[str, str | bytes] = Unicode(
164159
"<generated>",
165160
help=_i18n(
166161
"""Token used for authenticating first-time connections to the server.
@@ -211,9 +206,9 @@ def _token_default(self):
211206
self.token_generated = True
212207
return binascii.hexlify(os.urandom(24)).decode("ascii")
213208

214-
need_token: bool | Bool = Bool(True)
209+
need_token: bool | Bool[bool, t.Union[bool, int]] = Bool(True)
215210

216-
def get_user(self, handler: JupyterHandler) -> User | None | Awaitable[User | None]:
211+
def get_user(self, handler: web.RequestHandler) -> User | None | t.Awaitable[User | None]:
217212
"""Get the authenticated user for a request
218213
219214
Must return a :class:`jupyter_server.auth.User`,
@@ -228,17 +223,17 @@ def get_user(self, handler: JupyterHandler) -> User | None | Awaitable[User | No
228223
# not sure how to have optional-async type signature
229224
# on base class with `async def` without splitting it into two methods
230225

231-
async def _get_user(self, handler: JupyterHandler) -> User | None:
226+
async def _get_user(self, handler: web.RequestHandler) -> User | None:
232227
"""Get the user."""
233228
if getattr(handler, "_jupyter_current_user", None):
234229
# already authenticated
235-
return handler._jupyter_current_user
236-
_token_user: User | None | Awaitable[User | None] = self.get_user_token(handler)
237-
if isinstance(_token_user, Awaitable):
230+
return t.cast(User, handler._jupyter_current_user) # type:ignore[attr-defined]
231+
_token_user: User | None | t.Awaitable[User | None] = self.get_user_token(handler)
232+
if isinstance(_token_user, t.Awaitable):
238233
_token_user = await _token_user
239234
token_user: User | None = _token_user # need second variable name to collapse type
240235
_cookie_user = self.get_user_cookie(handler)
241-
if isinstance(_cookie_user, Awaitable):
236+
if isinstance(_cookie_user, t.Awaitable):
242237
_cookie_user = await _cookie_user
243238
cookie_user: User | None = _cookie_user
244239
# prefer token to cookie if both given,
@@ -273,12 +268,12 @@ async def _get_user(self, handler: JupyterHandler) -> User | None:
273268

274269
return user
275270

276-
def identity_model(self, user: User) -> dict:
271+
def identity_model(self, user: User) -> dict[str, t.Any]:
277272
"""Return a User as an Identity model"""
278273
# TODO: validate?
279274
return asdict(user)
280275

281-
def get_handlers(self) -> list:
276+
def get_handlers(self) -> list[tuple[str, object]]:
282277
"""Return list of additional handlers for this identity provider
283278
284279
For example, an OAuth callback handler.
@@ -321,7 +316,7 @@ def user_from_cookie(self, cookie_value: str) -> User | None:
321316
user["color"],
322317
)
323318

324-
def get_cookie_name(self, handler: AuthenticatedHandler) -> str:
319+
def get_cookie_name(self, handler: web.RequestHandler) -> str:
325320
"""Return the login cookie name
326321
327322
Uses IdentityProvider.cookie_name, if defined.
@@ -333,7 +328,7 @@ def get_cookie_name(self, handler: AuthenticatedHandler) -> str:
333328
else:
334329
return _non_alphanum.sub("-", f"username-{handler.request.host}")
335330

336-
def set_login_cookie(self, handler: AuthenticatedHandler, user: User) -> None:
331+
def set_login_cookie(self, handler: web.RequestHandler, user: User) -> None:
337332
"""Call this on handlers to set the login cookie for success"""
338333
cookie_options = {}
339334
cookie_options.update(self.cookie_options)
@@ -345,12 +340,12 @@ def set_login_cookie(self, handler: AuthenticatedHandler, user: User) -> None:
345340
secure_cookie = handler.request.protocol == "https"
346341
if secure_cookie:
347342
cookie_options.setdefault("secure", True)
348-
cookie_options.setdefault("path", handler.base_url)
343+
cookie_options.setdefault("path", handler.base_url) # type:ignore[attr-defined]
349344
cookie_name = self.get_cookie_name(handler)
350345
handler.set_secure_cookie(cookie_name, self.user_to_cookie(user), **cookie_options)
351346

352347
def _force_clear_cookie(
353-
self, handler: AuthenticatedHandler, name: str, path: str = "/", domain: str | None = None
348+
self, handler: web.RequestHandler, name: str, path: str = "/", domain: str | None = None
354349
) -> None:
355350
"""Deletes the cookie with the given name.
356351
@@ -368,19 +363,19 @@ def _force_clear_cookie(
368363
name = escape.native_str(name)
369364
expires = datetime.datetime.now(tz=datetime.timezone.utc) - datetime.timedelta(days=365)
370365

371-
morsel: Morsel = Morsel()
366+
morsel: Morsel[t.Any] = Morsel()
372367
morsel.set(name, "", '""')
373368
morsel["expires"] = httputil.format_timestamp(expires)
374369
morsel["path"] = path
375370
if domain:
376371
morsel["domain"] = domain
377372
handler.add_header("Set-Cookie", morsel.OutputString())
378373

379-
def clear_login_cookie(self, handler: AuthenticatedHandler) -> None:
374+
def clear_login_cookie(self, handler: web.RequestHandler) -> None:
380375
"""Clear the login cookie, effectively logging out the session."""
381376
cookie_options = {}
382377
cookie_options.update(self.cookie_options)
383-
path = cookie_options.setdefault("path", handler.base_url)
378+
path = cookie_options.setdefault("path", handler.base_url) # type:ignore[attr-defined]
384379
cookie_name = self.get_cookie_name(handler)
385380
handler.clear_cookie(cookie_name, path=path)
386381
if path and path != "/":
@@ -390,7 +385,9 @@ def clear_login_cookie(self, handler: AuthenticatedHandler) -> None:
390385
# two cookies with the same name. See the method above.
391386
self._force_clear_cookie(handler, cookie_name)
392387

393-
def get_user_cookie(self, handler: JupyterHandler) -> User | None | Awaitable[User | None]:
388+
def get_user_cookie(
389+
self, handler: web.RequestHandler
390+
) -> User | None | t.Awaitable[User | None]:
394391
"""Get user from a cookie
395392
396393
Calls user_from_cookie to deserialize cookie value
@@ -413,7 +410,7 @@ def get_user_cookie(self, handler: JupyterHandler) -> User | None | Awaitable[Us
413410

414411
auth_header_pat = re.compile(r"(token|bearer)\s+(.+)", re.IGNORECASE)
415412

416-
def get_token(self, handler: JupyterHandler) -> str | None:
413+
def get_token(self, handler: web.RequestHandler) -> str | None:
417414
"""Get the user token from a request
418415
419416
Default:
@@ -429,14 +426,14 @@ def get_token(self, handler: JupyterHandler) -> str | None:
429426
user_token = m.group(2)
430427
return user_token
431428

432-
async def get_user_token(self, handler: JupyterHandler) -> User | None:
429+
async def get_user_token(self, handler: web.RequestHandler) -> User | None:
433430
"""Identify the user based on a token in the URL or Authorization header
434431
435432
Returns:
436433
- uuid if authenticated
437434
- None if not
438435
"""
439-
token = handler.token
436+
token = t.cast("str | None", handler.token) # type:ignore[attr-defined]
440437
if not token:
441438
return None
442439
# check login token from URL argument or Authorization header
@@ -455,7 +452,7 @@ async def get_user_token(self, handler: JupyterHandler) -> User | None:
455452
# which is stored in a cookie.
456453
# still check the cookie for the user id
457454
_user = self.get_user_cookie(handler)
458-
if isinstance(_user, Awaitable):
455+
if isinstance(_user, t.Awaitable):
459456
_user = await _user
460457
user: User | None = _user
461458
if user is None:
@@ -464,7 +461,7 @@ async def get_user_token(self, handler: JupyterHandler) -> User | None:
464461
else:
465462
return None
466463

467-
def generate_anonymous_user(self, handler: JupyterHandler) -> User:
464+
def generate_anonymous_user(self, handler: web.RequestHandler) -> User:
468465
"""Generate a random anonymous user.
469466
470467
For use when a single shared token is used,
@@ -475,10 +472,10 @@ def generate_anonymous_user(self, handler: JupyterHandler) -> User:
475472
name = display_name = f"Anonymous {moon}"
476473
initials = f"A{moon[0]}"
477474
color = None
478-
handler.log.debug(f"Generating new user for token-authenticated request: {user_id}")
475+
handler.log.debug(f"Generating new user for token-authenticated request: {user_id}") # type:ignore[attr-defined]
479476
return User(user_id, name, display_name, initials, None, color)
480477

481-
def should_check_origin(self, handler: AuthenticatedHandler) -> bool:
478+
def should_check_origin(self, handler: web.RequestHandler) -> bool:
482479
"""Should the Handler check for CORS origin validation?
483480
484481
Origin check should be skipped for token-authenticated requests.
@@ -489,7 +486,7 @@ def should_check_origin(self, handler: AuthenticatedHandler) -> bool:
489486
"""
490487
return not self.is_token_authenticated(handler)
491488

492-
def is_token_authenticated(self, handler: AuthenticatedHandler) -> bool:
489+
def is_token_authenticated(self, handler: web.RequestHandler) -> bool:
493490
"""Returns True if handler has been token authenticated. Otherwise, False.
494491
495492
Login with a token is used to signal certain things, such as:
@@ -504,8 +501,8 @@ def is_token_authenticated(self, handler: AuthenticatedHandler) -> bool:
504501

505502
def validate_security(
506503
self,
507-
app: ServerApp,
508-
ssl_options: dict | None = None,
504+
app: t.Any,
505+
ssl_options: dict[str, t.Any] | None = None,
509506
) -> None:
510507
"""Check the application's security.
511508
@@ -526,7 +523,7 @@ def validate_security(
526523
" Anyone who can connect to this server will be able to run code."
527524
)
528525

529-
def process_login_form(self, handler: JupyterHandler) -> User | None:
526+
def process_login_form(self, handler: web.RequestHandler) -> User | None:
530527
"""Process login form data
531528
532529
Return authenticated User if successful, None if not.
@@ -538,7 +535,7 @@ def process_login_form(self, handler: JupyterHandler) -> User | None:
538535
return self.generate_anonymous_user(handler)
539536

540537
if self.token and self.token == typed_password:
541-
return self.user_for_token(typed_password) # type:ignore[attr-defined]
538+
return t.cast(User, self.user_for_token(typed_password)) # type:ignore[attr-defined]
542539

543540
return user
544541

@@ -633,7 +630,7 @@ def passwd_check(self, password):
633630
"""Check password against our stored hashed password"""
634631
return passwd_check(self.hashed_password, password)
635632

636-
def process_login_form(self, handler: JupyterHandler) -> User | None:
633+
def process_login_form(self, handler: web.RequestHandler) -> User | None:
637634
"""Process login form data
638635
639636
Return authenticated User if successful, None if not.
@@ -659,8 +656,8 @@ def process_login_form(self, handler: JupyterHandler) -> User | None:
659656

660657
def validate_security(
661658
self,
662-
app: ServerApp,
663-
ssl_options: dict | None = None,
659+
app: t.Any,
660+
ssl_options: dict[str, t.Any] | None = None,
664661
) -> None:
665662
"""Handle security validation."""
666663
super().validate_security(app, ssl_options)
@@ -700,31 +697,33 @@ def _default_login_handler_class(self):
700697
def auth_enabled(self):
701698
return self.login_available
702699

703-
def get_user(self, handler: JupyterHandler) -> User | None:
700+
def get_user(self, handler: web.RequestHandler) -> User | None:
704701
"""Get the user."""
705702
user = self.login_handler_class.get_user(handler) # type:ignore[attr-defined]
706703
if user is None:
707704
return None
708705
return _backward_compat_user(user)
709706

710707
@property
711-
def login_available(self):
712-
return self.login_handler_class.get_login_available( # type:ignore[attr-defined]
713-
self.settings
708+
def login_available(self) -> bool:
709+
return bool(
710+
self.login_handler_class.get_login_available( # type:ignore[attr-defined]
711+
self.settings
712+
)
714713
)
715714

716-
def should_check_origin(self, handler: AuthenticatedHandler) -> bool:
715+
def should_check_origin(self, handler: web.RequestHandler) -> bool:
717716
"""Whether we should check origin."""
718-
return self.login_handler_class.should_check_origin(handler) # type:ignore[attr-defined]
717+
return bool(self.login_handler_class.should_check_origin(handler)) # type:ignore[attr-defined]
719718

720-
def is_token_authenticated(self, handler: AuthenticatedHandler) -> bool:
719+
def is_token_authenticated(self, handler: web.RequestHandler) -> bool:
721720
"""Whether we are token authenticated."""
722-
return self.login_handler_class.is_token_authenticated(handler) # type:ignore[attr-defined]
721+
return bool(self.login_handler_class.is_token_authenticated(handler)) # type:ignore[attr-defined]
723722

724723
def validate_security(
725724
self,
726-
app: ServerApp,
727-
ssl_options: dict | None = None,
725+
app: t.Any,
726+
ssl_options: dict[str, t.Any] | None = None,
728727
) -> None:
729728
"""Validate security."""
730729
if self.password_required and (not self.hashed_password):
@@ -734,6 +733,6 @@ def validate_security(
734733
self.log.critical(_i18n("Hint: run the following command to set a password"))
735734
self.log.critical(_i18n("\t$ python -m jupyter_server.auth password"))
736735
sys.exit(1)
737-
return self.login_handler_class.validate_security( # type:ignore[attr-defined]
736+
self.login_handler_class.validate_security( # type:ignore[attr-defined]
738737
app, ssl_options
739738
)

0 commit comments

Comments
 (0)