diff --git a/pycroft/lib/finance/membership_fee.py b/pycroft/lib/finance/membership_fee.py index 6956cf198..869dc9c3d 100644 --- a/pycroft/lib/finance/membership_fee.py +++ b/pycroft/lib/finance/membership_fee.py @@ -250,14 +250,12 @@ def post_transactions_for_membership_fee( ).fetchall() if not simulate: - # `over` not typed yet, - # see https://github.com/sqlalchemy/sqlalchemy/issues/6810 numbered_users = ( select( users.c.id, users.c.fee_account_id.label("fee_account_id"), users.c.account_id, - func.row_number().over().label("index"), # type: ignore[no-untyped-call] + func.row_number().over().label("index"), ) .select_from(users) .cte("membership_fee_numbered_users") @@ -289,7 +287,7 @@ def post_transactions_for_membership_fee( numbered_transactions = ( select( transactions.c.id, - func.row_number().over().label("index"), # type: ignore[no-untyped-call] + func.row_number().over().label("index"), ) .select_from(transactions) .cte("membership_fee_numbered_transactions") diff --git a/pycroft/lib/user_deletion.py b/pycroft/lib/user_deletion.py index bf831f9dd..cb8c57536 100644 --- a/pycroft/lib/user_deletion.py +++ b/pycroft/lib/user_deletion.py @@ -5,6 +5,7 @@ This module contains methods concerning user archival and deletion. """ from __future__ import annotations +import typing as t from datetime import timedelta, datetime from typing import Protocol, Sequence @@ -24,6 +25,16 @@ class ArchivableMemberInfo(Protocol): mem_end: datetime +TP = t.TypeVar("TP") +TO = t.TypeVar("TO") + + +@t.type_check_only +class _WindowArgs(t.TypedDict, t.Generic[TP, TO]): + partition_by: TP + order_by: TO + + def get_archivable_members(session: Session) -> Sequence[ArchivableMemberInfo]: """Return all the users that qualify for being archived right now. @@ -33,28 +44,25 @@ def get_archivable_members(session: Session) -> Sequence[ArchivableMemberInfo]: """ # see FunctionElement.over mem_ends_at = func.upper(Membership.active_during) - window_args = { + window_args: _WindowArgs = { 'partition_by': User.id, 'order_by': nulls_last(mem_ends_at), } - # mypy: ignore[no-untyped-call] last_mem = ( select( User.id.label('user_id'), func.last_value(Membership.id) - .over(**window_args, rows=(None, None)) # type: ignore[no-untyped-call] + .over(**window_args, rows=(None, None)) .label("mem_id"), func.last_value(mem_ends_at) - .over(**window_args, rows=(None, None)) # type: ignore[no-untyped-call] + .over(**window_args, rows=(None, None)) .label("mem_end"), ) .select_from(User) .distinct() .join(Membership) .join(Config, Config.member_group_id == Membership.group_id) - ).cte( - "last_mem" - ) # mypy: ignore[no-untyped-call] + ).cte("last_mem") stmt = ( select( User,