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

stricter mypy rules for web package #663

Merged
merged 6 commits into from
Sep 5, 2023
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions hades_logs/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import logging

from celery.exceptions import TimeoutError as CeleryTimeoutError
from flask import Flask
from flask.globals import current_app
from kombu.exceptions import OperationalError
from werkzeug.local import LocalProxy
Expand Down Expand Up @@ -62,13 +63,14 @@ class HadesLogs:

>>> logs.fetch_logs(<nasip>, <portid>)
"""
def __init__(self, app=None):

def __init__(self, app: Flask | None = None) -> None:
self.app = app
self.logger = logging.getLogger('hades_logs')
if app is not None:
self.init_app(app)

def init_app(self, app):
def init_app(self, app: Flask) -> None:
try:
app_name = app.config['HADES_CELERY_APP_NAME']
broker_uri = app.config['HADES_BROKER_URI']
Expand Down
6 changes: 4 additions & 2 deletions pycroft/lib/user.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright (c) 2015 The Pycroft Authors. See the AUTHORS file.

Check warning on line 1 in pycroft/lib/user.py

View workflow job for this annotation

GitHub Actions / python-lint

Warning

docstring of pycroft.lib.user.edit_email:8: Field list ends without a blank line; unexpected unindent.

Check warning on line 1 in pycroft/lib/user.py

View workflow job for this annotation

GitHub Actions / python-lint

Warning

docstring of pycroft.lib.user.edit_birthdate:5: Field list ends without a blank line; unexpected unindent.
# 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.
"""
Expand All @@ -17,7 +17,7 @@
from difflib import SequenceMatcher
from typing import Iterable

from sqlalchemy import func, select, Boolean, String
from sqlalchemy import func, select, Boolean, String, ColumnElement

from pycroft import config, property
from pycroft.helpers import user as user_helper, utc
Expand Down Expand Up @@ -681,7 +681,9 @@


def traffic_history(
user_id: int, start: DateTimeTz, end: DateTimeTz
user_id: int,
start: DateTimeTz | ColumnElement[DateTimeTz],
end: DateTimeTz | ColumnElement[DateTimeTz],
) -> list[TrafficHistoryEntry]:
result = session.session.execute(
select("*")
Expand Down
6 changes: 3 additions & 3 deletions pycroft/model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from datetime import timezone, tzinfo

import psycopg2.extensions
from sqlalchemy import create_engine as sqa_create_engine
from sqlalchemy import create_engine as sqa_create_engine, Connection
from sqlalchemy.future import Engine

from . import _all
Expand Down Expand Up @@ -61,7 +61,7 @@ def create_engine(connection_string, **kwargs) -> Engine:
return sqa_create_engine(connection_string, **kwargs)


def create_db_model(bind):
def create_db_model(bind: Connection) -> None:
"""Create all models in the database.
"""
# skip objects marked with "is_view"
Expand All @@ -70,7 +70,7 @@ def create_db_model(bind):
base.ModelBase.metadata.create_all(bind, tables=tables)


def drop_db_model(bind):
def drop_db_model(bind: Connection) -> None:
"""Drop all models from the database.
"""
# skip objects marked with "is_view"
Expand Down
12 changes: 7 additions & 5 deletions pycroft/model/finance.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@
import datetime
import typing as t
from datetime import timedelta, date
from decimal import Decimal
from math import fabs

from sqlalchemy import ForeignKey, event, func, select, Enum, ColumnElement
from sqlalchemy import ForeignKey, event, func, select, Enum, ColumnElement, Select
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import relationship, object_session, Mapped, mapped_column
from sqlalchemy.schema import CheckConstraint, ForeignKeyConstraint, UniqueConstraint
Expand All @@ -25,6 +26,7 @@
from .exc import PycroftModelException
from .type_aliases import str127, str255, datetime_tz_onupdate
from ..helpers import utc
from ..helpers.utc import DateTimeTz

manager = ddl.DDLManager()

Expand Down Expand Up @@ -361,22 +363,22 @@ class BankAccount(IntegerIdModel):
# /backrefs

@hybrid_property
def balance(self):
def _balance(self) -> Decimal:
return object_session(self).execute(
select(func.coalesce(func.sum(BankAccountActivity.amount), 0))
.where(BankAccountActivity.bank_account_id == self.id)
).scalar()

@balance.expression
def balance(cls):
@_balance.expression
def balance(cls) -> Select[tuple[Decimal]]:
return select(
[func.coalesce(func.sum(BankAccountActivity.amount), 0)]
).where(
BankAccountActivity.bank_account_id == cls.id
).label("balance")

@hybrid_property
def last_imported_at(self):
def last_imported_at(self) -> DateTimeTz:
return object_session(self).execute(
select(func.max(BankAccountActivity.imported_at))
.where(BankAccountActivity.bank_account_id == self.id)
Expand Down
5 changes: 5 additions & 0 deletions pycroft/model/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from typing import overload, TypeVar, Callable, Any, TYPE_CHECKING

from sqlalchemy.orm import scoped_session
from sqlalchemy.sql.functions import AnsiFunction
from werkzeug.local import LocalProxy
import wrapt

Expand Down Expand Up @@ -77,3 +78,7 @@ def with_transaction(wrapped, instance, args, kwargs):

def utcnow() -> DateTimeTz:
return session.query(func.current_timestamp()).scalar()


def current_timestamp() -> AnsiFunction[DateTimeTz]:
return t.cast(AnsiFunction[DateTimeTz], func.current_timestamp())
6 changes: 5 additions & 1 deletion pycroft/model/traffic.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
select,
cast,
TEXT,
ColumnElement,
)
from sqlalchemy.orm import relationship, Query, Mapped, mapped_column
from sqlalchemy.sql.selectable import TableValuedAlias
Expand Down Expand Up @@ -224,7 +225,10 @@ def traffic_history_query():


def traffic_history(
user_id: int, start: utc.DateTimeTz, end: utc.DateTimeTz, name='traffic_history'
user_id: int,
start: utc.DateTimeTz | ColumnElement[utc.DateTimeTz],
end: utc.DateTimeTz | ColumnElement[utc.DateTimeTz],
name="traffic_history",
) -> TableValuedAlias:
"""A sqlalchemy `func` wrapper for the `evaluate_properties` PSQL function.
Expand Down
5 changes: 3 additions & 2 deletions pycroft/model/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,14 +185,15 @@ def validate_passwd_hash(self, _, value):
"not correct!"
return value

def check_password(self, plaintext_password):
def check_password(self, plaintext_password: str) -> bool:
"""verify a given plaintext password against the users passwd hash.

"""
return verify_password(plaintext_password, self.passwd_hash)

@property
def password(self):
# actually `NoReturn`, but mismatch to `setter` confuses mypy
def password(self) -> str:
"""Store a hash of a given plaintext passwd for the user."""
raise RuntimeError("Password can not be read, only set")

Expand Down
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ files = [
"pycroft/lib",
"pycroft/external_services",
"ldap_sync",
"web/blueprints",
"web",
]
mypy_path = "stubs"
namespace_packages = true
Expand Down Expand Up @@ -49,8 +49,8 @@ module = [
"pycroft.helpers.task",
"pycroft.helpers.user",
"pycroft.helpers.utc",
"web.blueprints",
"web.blueprints.*",
"web",
"web.*",
]
disallow_untyped_defs = true
disallow_untyped_calls = true
Expand Down
5 changes: 4 additions & 1 deletion stubs/flask_login/login_manager.pyi
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import typing as t

from flask.typing import ResponseReturnValue

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
Expand All @@ -22,7 +25,7 @@ class LoginManager:
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): ...
def unauthorized(self) -> ResponseReturnValue: ...
_C = t.TypeVar("_C")
def user_loader(self, callback: _C) -> _C: ...
@property
Expand Down
54 changes: 54 additions & 0 deletions stubs/flask_restful/__init__.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
from _typeshed import Incomplete

from flask import Response
from flask.views import MethodView

def abort(http_status_code, **kwargs) -> None: ...

class Api:
representations: Incomplete
urls: Incomplete
prefix: Incomplete
default_mediatype: Incomplete
decorators: Incomplete
catch_all_404s: Incomplete
serve_challenge_on_401: Incomplete
url_part_order: Incomplete
errors: Incomplete
blueprint_setup: Incomplete
endpoints: Incomplete
resources: Incomplete
app: Incomplete
blueprint: Incomplete
def __init__(self, app: Incomplete | None = ..., prefix: str = ..., default_mediatype: str = ..., decorators: Incomplete | None = ..., catch_all_404s: bool = ..., serve_challenge_on_401: bool = ..., url_part_order: str = ..., errors: Incomplete | None = ...) -> None: ...
def init_app(self, app) -> None: ...
def owns_endpoint(self, endpoint): ...
def error_router(self, original_handler, e): ...
def handle_error(self, e: Exception) -> Response: ...
def mediatypes_method(self): ...
def add_resource(self, resource, *urls, **kwargs) -> None: ...
def resource(self, *urls, **kwargs): ...
def output(self, resource): ...
def url_for(self, resource, **values): ...
def make_response(self, data, *args, **kwargs): ...
def mediatypes(self): ...
def representation(self, mediatype): ...
def unauthorized(self, response): ...

class Resource(MethodView):
representations: Incomplete
method_decorators: Incomplete
def dispatch_request(self, *args, **kwargs): ...

def marshal(data, fields, envelope: Incomplete | None = ...): ...

class marshal_with:
fields: Incomplete
envelope: Incomplete
def __init__(self, fields, envelope: Incomplete | None = ...) -> None: ...
def __call__(self, f): ...

class marshal_with_field:
field: Incomplete
def __init__(self, field) -> None: ...
def __call__(self, f): ...
Empty file.
62 changes: 62 additions & 0 deletions stubs/flask_restful/fields.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
from _typeshed import Incomplete

class MarshallingException(Exception):
def __init__(self, underlying_exception) -> None: ...

class Raw:
attribute: Incomplete
default: Incomplete
def __init__(self, default: Incomplete | None = ..., attribute: Incomplete | None = ...) -> None: ...
def format(self, value): ...
def output(self, key, obj): ...

class Nested(Raw):
nested: Incomplete
allow_null: Incomplete
def __init__(self, nested, allow_null: bool = ..., **kwargs) -> None: ...
def output(self, key, obj): ...

class List(Raw):
container: Incomplete
def __init__(self, cls_or_instance, **kwargs) -> None: ...
def format(self, value): ...
def output(self, key, data): ...

class String(Raw):
def format(self, value): ...

class Integer(Raw):
def __init__(self, default: int = ..., **kwargs) -> None: ...
def format(self, value): ...

class Boolean(Raw):
def format(self, value): ...

class FormattedString(Raw):
src_str: Incomplete
def __init__(self, src_str) -> None: ...
def output(self, key, obj): ...

class Url(Raw):
endpoint: Incomplete
absolute: Incomplete
scheme: Incomplete
def __init__(self, endpoint: Incomplete | None = ..., absolute: bool = ..., scheme: Incomplete | None = ..., **kwargs) -> None: ...
def output(self, key, obj): ...

class Float(Raw):
def format(self, value): ...

class Arbitrary(Raw):
def format(self, value): ...

class DateTime(Raw):
dt_format: Incomplete
def __init__(self, dt_format: str = ..., **kwargs) -> None: ...
def format(self, value): ...

class Fixed(Raw):
precision: Incomplete
def __init__(self, decimals: int = ..., **kwargs) -> None: ...
def format(self, value): ...
Price = Fixed
31 changes: 31 additions & 0 deletions stubs/flask_restful/inputs.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from _typeshed import Incomplete
from calendar import timegm as timegm

START_OF_DAY: Incomplete
END_OF_DAY: Incomplete
url_regex: Incomplete

def url(value): ...

class regex:
pattern: Incomplete
re: Incomplete
def __init__(self, pattern, flags: int = ...) -> None: ...
def __call__(self, value): ...
def __deepcopy__(self, memo): ...

def iso8601interval(value, argument: str = ...): ...
def date(value): ...
def natural(value, argument: str = ...): ...
def positive(value, argument: str = ...): ...

class int_range:
low: Incomplete
high: Incomplete
argument: Incomplete
def __init__(self, low, high, argument: str = ...) -> None: ...
def __call__(self, value): ...

def boolean(value): ...
def datetime_from_rfc822(datetime_str): ...
def datetime_from_iso8601(datetime_str): ...
Empty file.
4 changes: 4 additions & 0 deletions stubs/flask_restful/representations/json.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from _typeshed import Incomplete
from flask_restful.utils import PY3 as PY3

def output_json(data, code, headers: Incomplete | None = ...): ...
Loading
Loading