Skip to content

Commit

Permalink
Merge branch 'mypy_web' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
lukasjuhrich committed Aug 27, 2023
2 parents 4ec9170 + 6a5ae15 commit 9f3abb5
Show file tree
Hide file tree
Showing 43 changed files with 676 additions and 352 deletions.
5 changes: 4 additions & 1 deletion hades_logs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
This module provides access to Hades' radius logs utilizing its celery
RPC api.
"""
import typing as t
import logging

from celery.exceptions import TimeoutError as CeleryTimeoutError
Expand Down Expand Up @@ -110,7 +111,9 @@ def create_task(self, name, *args, **kwargs):
full_task_name = f'{self.celery.main}.{name}'
return self.celery.signature(full_task_name, args=args, kwargs=kwargs)

def fetch_logs(self, nasipaddress: str, nasportid: str, limit=100, reduced=True):
def fetch_logs(
self, nasipaddress: str, nasportid: str, limit: int = 100, reduced: bool = True
) -> t.Iterator[RadiusLogEntry]:
"""Fetch the auth logs of the given port
:param ipaddr nasipaddress: The IP address of the NAS
Expand Down
9 changes: 6 additions & 3 deletions pycroft/lib/finance/matching.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# the Apache License, Version 2.0. See the LICENSE file for details
import logging
import re
import typing as t
from typing import Callable, TypeVar

from sqlalchemy import select
Expand All @@ -17,9 +18,11 @@

T = TypeVar("T")

def match_activities() -> (
tuple[dict[BankAccountActivity, User], dict[BankAccountActivity, Account]]
):
UserMatching: t.TypeAlias = dict[BankAccountActivity, User]
AccountMatching: t.TypeAlias = dict[BankAccountActivity, Account]


def match_activities() -> tuple[UserMatching, AccountMatching]:
"""For all unmatched transactions, determine which user or team they should be matched with."""
matching: dict[BankAccountActivity, User] = {}
team_matching: dict[BankAccountActivity, Account] = {}
Expand Down
2 changes: 1 addition & 1 deletion pycroft/lib/finance/transaction_crud.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def simple_transaction(
description: str,
debit_account: Account,
credit_account: Account,
amount: Decimal,
amount: Decimal | int,
author: User,
valid_on: date | None = None,
confirmed: bool = True,
Expand Down
3 changes: 2 additions & 1 deletion pycroft/lib/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -919,11 +919,12 @@ def generate_user_sheet(
Only necessary if wifi=True:
:param generation_purpose: Optional purpose why this usersheet was printed
"""
from pycroft.helpers import printing
return generate_pdf(
new_user=new_user,
wifi=wifi,
bank_account=config.membership_fee_bank_account,
user=user,
user=t.cast(printing.User, user),
user_id=encode_type2_user_id(user.id),
plain_user_password=plain_user_password,
generation_purpose=generation_purpose,
Expand Down
2 changes: 1 addition & 1 deletion pycroft/model/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,7 @@ def has_wifi_access(self):
return self.wifi_passwd_hash is not None

@staticmethod
def verify_and_get(login, plaintext_password):
def verify_and_get(login: str, plaintext_password: str) -> User | None:
try:
user = User.q.filter(User.login == func.lower(login)).one()
except NoResultFound:
Expand Down
5 changes: 1 addition & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,7 @@ module = [
"pycroft.helpers.user",
"pycroft.helpers.utc",
"web.blueprints",
"web.blueprints.access",
"web.blueprints.facilities",
# "web.blueprints.host",
"web.blueprints.navigation",
"web.blueprints.*",
]
disallow_untyped_defs = true
disallow_untyped_calls = true
Expand Down
7 changes: 7 additions & 0 deletions stubs/flask_login/__about__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from _typeshed import Incomplete

__description__: str
__url__: str
__version_info__: Incomplete
__author_email__: str
__maintainer__: str
5 changes: 5 additions & 0 deletions stubs/flask_login/__init__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from .config import AUTH_HEADER_NAME as AUTH_HEADER_NAME, COOKIE_DURATION as COOKIE_DURATION, COOKIE_HTTPONLY as COOKIE_HTTPONLY, COOKIE_NAME as COOKIE_NAME, COOKIE_SECURE as COOKIE_SECURE, ID_ATTRIBUTE as ID_ATTRIBUTE, LOGIN_MESSAGE as LOGIN_MESSAGE, LOGIN_MESSAGE_CATEGORY as LOGIN_MESSAGE_CATEGORY, REFRESH_MESSAGE as REFRESH_MESSAGE, REFRESH_MESSAGE_CATEGORY as REFRESH_MESSAGE_CATEGORY
from .login_manager import LoginManager as LoginManager
from .mixins import AnonymousUserMixin as AnonymousUserMixin, UserMixin as UserMixin
from .signals import session_protected as session_protected, user_accessed as user_accessed, user_loaded_from_cookie as user_loaded_from_cookie, user_loaded_from_request as user_loaded_from_request, user_logged_in as user_logged_in, user_logged_out as user_logged_out, user_login_confirmed as user_login_confirmed, user_needs_refresh as user_needs_refresh, user_unauthorized as user_unauthorized
from .utils import confirm_login as confirm_login, current_user as current_user, decode_cookie as decode_cookie, encode_cookie as encode_cookie, fresh_login_required as fresh_login_required, login_fresh as login_fresh, login_remembered as login_remembered, login_required as login_required, login_url as login_url, login_user as login_user, logout_user as logout_user, make_next_param as make_next_param, set_login_view as set_login_view
16 changes: 16 additions & 0 deletions stubs/flask_login/config.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from _typeshed import Incomplete

COOKIE_NAME: str
COOKIE_DURATION: Incomplete
COOKIE_SECURE: bool
COOKIE_HTTPONLY: bool
COOKIE_SAMESITE: Incomplete
LOGIN_MESSAGE: str
LOGIN_MESSAGE_CATEGORY: str
REFRESH_MESSAGE: str
REFRESH_MESSAGE_CATEGORY: str
ID_ATTRIBUTE: str
AUTH_HEADER_NAME: str
SESSION_KEYS: Incomplete
EXEMPT_METHODS: Incomplete
USE_SESSION_FOR_NEXT: bool
36 changes: 36 additions & 0 deletions stubs/flask_login/login_manager.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import typing as t
from .config import AUTH_HEADER_NAME as AUTH_HEADER_NAME, COOKIE_DURATION as COOKIE_DURATION, COOKIE_HTTPONLY as COOKIE_HTTPONLY, COOKIE_NAME as COOKIE_NAME, COOKIE_SAMESITE as COOKIE_SAMESITE, COOKIE_SECURE as COOKIE_SECURE, ID_ATTRIBUTE as ID_ATTRIBUTE, LOGIN_MESSAGE as LOGIN_MESSAGE, LOGIN_MESSAGE_CATEGORY as LOGIN_MESSAGE_CATEGORY, REFRESH_MESSAGE as REFRESH_MESSAGE, REFRESH_MESSAGE_CATEGORY as REFRESH_MESSAGE_CATEGORY, SESSION_KEYS as SESSION_KEYS, USE_SESSION_FOR_NEXT as USE_SESSION_FOR_NEXT
from .mixins import AnonymousUserMixin as AnonymousUserMixin
from .signals import session_protected as session_protected, user_accessed as user_accessed, user_loaded_from_cookie as user_loaded_from_cookie, user_loaded_from_request as user_loaded_from_request, user_needs_refresh as user_needs_refresh, user_unauthorized as user_unauthorized
from .utils import decode_cookie as decode_cookie, encode_cookie as encode_cookie, expand_login_view as expand_login_view, make_next_param as make_next_param
from _typeshed import Incomplete

class LoginManager:
anonymous_user: Incomplete
login_view: Incomplete
blueprint_login_views: Incomplete
login_message: Incomplete
login_message_category: Incomplete
refresh_view: Incomplete
needs_refresh_message: Incomplete
needs_refresh_message_category: Incomplete
session_protection: str
localize_callback: Incomplete
unauthorized_callback: Incomplete
needs_refresh_callback: Incomplete
id_attribute: Incomplete
def __init__(self, app: Incomplete | None = ..., add_context_processor: bool = ...) -> None: ...
def setup_app(self, app, add_context_processor: bool = ...) -> None: ...
def init_app(self, app, add_context_processor: bool = ...) -> None: ...
def unauthorized(self): ...
_C = t.TypeVar("_C")
def user_loader(self, callback: _C) -> _C: ...
@property
def user_callback(self): ...
def request_loader(self, callback): ...
@property
def request_callback(self): ...
def unauthorized_handler(self, callback): ...
def needs_refresh_handler(self, callback): ...
def needs_refresh(self): ...
def header_loader(self, callback): ...
22 changes: 22 additions & 0 deletions stubs/flask_login/mixins.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
from _typeshed import Incomplete

class UserMixin:
__hash__: Incomplete
@property
def is_active(self): ...
@property
def is_authenticated(self): ...
@property
def is_anonymous(self): ...
def get_id(self): ...
def __eq__(self, other): ...
def __ne__(self, other): ...

class AnonymousUserMixin:
@property
def is_authenticated(self): ...
@property
def is_active(self): ...
@property
def is_anonymous(self): ...
def get_id(self) -> None: ...
13 changes: 13 additions & 0 deletions stubs/flask_login/signals.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from _typeshed import Incomplete

user_logged_in: Incomplete
user_logged_out: Incomplete
user_loaded_from_cookie: Incomplete
user_loaded_from_request: Incomplete
user_login_confirmed: Incomplete
user_unauthorized: Incomplete
user_needs_refresh: Incomplete
user_accessed: Incomplete
session_protected: Incomplete

def __getattr__(name): ...
23 changes: 23 additions & 0 deletions stubs/flask_login/utils.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import typing as t
from .config import COOKIE_NAME as COOKIE_NAME, EXEMPT_METHODS as EXEMPT_METHODS
from .signals import user_logged_in as user_logged_in, user_logged_out as user_logged_out, user_login_confirmed as user_login_confirmed
from _typeshed import Incomplete

current_user: Incomplete

def encode_cookie(payload, key: Incomplete | None = ...): ...
def decode_cookie(cookie, key: Incomplete | None = ...): ...
def make_next_param(login_url, current_url): ...
def expand_login_view(login_view): ...
def login_url(login_view, next_url: Incomplete | None = ..., next_field: str = ...): ...
def login_fresh(): ...
def login_remembered(): ...
def login_user(user, remember: bool = ..., duration: Incomplete | None = ..., force: bool = ..., fresh: bool = ...): ...
def logout_user() -> bool: ...
def confirm_login() -> None: ...

_F = t.TypeVar("_F")

def login_required(func: _F) -> _F: ...
def fresh_login_required(func): ...
def set_login_view(login_view, blueprint: Incomplete | None = ...) -> None: ...
12 changes: 9 additions & 3 deletions tests/table/test_bootstrap_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ def test_table_args_set(self, EmptyTable):
}

def test_table_args_after_instantiation(self, EmptyTable):
assert EmptyTable("#").table_args == {
assert EmptyTable(data_url="#").table_args == {
'data-toggle': "table",
"data-icons-prefix": "fa",
'data-url': "#",
Expand Down Expand Up @@ -250,10 +250,16 @@ class Meta:
return A_

def test_url_param_is_added(self, A: type[BootstrapTable]):
assert A("http://localhost/table").data_url == "http://localhost/table?inverted=yes"
assert (
A(data_url="http://localhost/table").data_url
== "http://localhost/table?inverted=yes"
)

def test_url_param_is_overridden(self, A: type[BootstrapTable]):
assert A("http://localhost/table?inverted=no").data_url == "http://localhost/table?inverted=yes"
assert (
A(data_url="http://localhost/table?inverted=no").data_url
== "http://localhost/table?inverted=yes"
)


class TestSplittedTable:
Expand Down
9 changes: 6 additions & 3 deletions web/blueprints/facilities/address.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from sqlalchemy.orm import Session
from sqlalchemy.orm import Session, Query
from sqlalchemy.orm.attributes import InstrumentedAttribute

from pycroft.model.address import Address
Expand All @@ -24,8 +24,11 @@ def get_address_entity(type: str) -> InstrumentedAttribute:
) from None


def address_entity_search_query(query: str, entity: InstrumentedAttribute, session: Session, limit: int):
return (session.query()
def address_entity_search_query(
query: str, entity: InstrumentedAttribute, session: Session, limit: int
) -> Query:
return (
session.query()
.add_columns(entity)
.distinct()
.filter(entity.ilike(f"%{query}%"))
Expand Down
15 changes: 9 additions & 6 deletions web/blueprints/facilities/forms.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright (c) 2015 The Pycroft Authors. See the AUTHORS file.
# This file is part of the Pycroft project and licensed under the terms of
# the Apache License, Version 2.0. See the LICENSE file for details.
import typing as t
from typing import Callable

from flask import url_for
Expand All @@ -21,14 +22,16 @@


class LazyString:
def __init__(self, value_factory: Callable[[], str]):
def __init__(self, value_factory: Callable[[], str]) -> None:
self.value_factory = value_factory

def __str__(self):
def __str__(self) -> str:
return self.value_factory()


def create_address_field(name: str, *args, type: str, render_kw: dict | None = None, **kwargs):
def create_address_field(
name: str, *args: t.Any, type: str, render_kw: dict | None = None, **kwargs: t.Any
) -> TypeaheadField:
assert type in ADDRESS_ENTITIES, "Unknown address_type"
return TypeaheadField(
name,
Expand Down Expand Up @@ -57,11 +60,11 @@ class CreateAddressForm(BaseForm):
address_country = create_address_field("Land", type="country")

@property
def address_kwargs(self):
def address_kwargs(self) -> dict[str, str]:
return {key: getattr(self, f'address_{key}').data
for key in 'street number addition zip_code city state country'.split()}

def set_address_fields(self, obj: RoomAddressSuggestion | Address | None):
def set_address_fields(self, obj: RoomAddressSuggestion | Address | None) -> None:
if not obj:
return
self.address_street.data = obj.street
Expand All @@ -75,7 +78,7 @@ def set_address_fields(self, obj: RoomAddressSuggestion | Address | None):
self.address_addition.data = addition


def building_query():
def building_query() -> list[Building]:
return sort_buildings(Building.q.all())


Expand Down
13 changes: 8 additions & 5 deletions web/blueprints/facilities/tables.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import typing as t

from flask import url_for
from pydantic import BaseModel

from web.table.lazy_join import LazilyJoined
from web.table.table import (
BootstrapTable,
Column,
Expand Down Expand Up @@ -37,7 +40,7 @@ class Meta:
inhabitants = MultiBtnColumn('Bewohner')

@property
def toolbar(self):
def toolbar(self) -> LazilyJoined:
return toggle_button_toolbar(
"Display all users",
id="rooms-toggle-all-users",
Expand Down Expand Up @@ -82,15 +85,15 @@ class Meta:
edit_link = BtnColumn('Editieren', hide_if=no_inf_change)
delete_link = BtnColumn('Löschen', hide_if=no_inf_change)

def __init__(self, *a, room_id=None, **kw) -> None:
super().__init__(*a, **kw)
def __init__(self, *, room_id: int | None = None, **kw: t.Any) -> None:
super().__init__(**kw)

self.room_id = room_id

@property
def toolbar(self):
def toolbar(self) -> LazilyJoined | None:
if no_inf_change():
return
return None
href = url_for(".patch_port_create", switch_room_id=self.room_id)
return button_toolbar("Patch-Port", href)

Expand Down
Loading

0 comments on commit 9f3abb5

Please sign in to comment.