diff --git a/src/accounting/account/commands.py b/src/accounting/account/commands.py index b7abbe7..f30c80a 100644 --- a/src/accounting/account/commands.py +++ b/src/accounting/account/commands.py @@ -17,15 +17,15 @@ """The console commands for the account management. """ -import typing as t from secrets import randbelow +from typing import Any import click +import sqlalchemy as sa from accounting import db from accounting.models import BaseAccount, Account, AccountL10n from accounting.utils.user import get_user_pk -import sqlalchemy as sa AccountData = tuple[int, str, int, str, str, str, bool] """The format of the account data, as a list of (ID, base account code, number, @@ -63,8 +63,8 @@ def init_accounts_command(username: str) -> None: existing_id.add(new_id) return new_id - data: list[dict[str, t.Any]] = [] - l10n_data: list[dict[str, t.Any]] = [] + data: list[dict[str, Any]] = [] + l10n_data: list[dict[str, Any]] = [] for base in bases_to_add: l10n: dict[str, str] = {x.locale: x.title for x in base.l10n} account_id: int = get_new_id() diff --git a/src/accounting/currency/commands.py b/src/accounting/currency/commands.py index 0c2541b..1ff9a85 100644 --- a/src/accounting/currency/commands.py +++ b/src/accounting/currency/commands.py @@ -18,7 +18,7 @@ """ import csv -import typing as t +from typing import Any import sqlalchemy as sa @@ -39,11 +39,11 @@ def init_currencies_command(username: str) -> None: return creator_pk: int = get_user_pk(username) - currency_data: list[dict[str, t.Any]] = [{"code": x["code"], - "name_l10n": x["name"], - "created_by_id": creator_pk, - "updated_by_id": creator_pk} - for x in to_add] + currency_data: list[dict[str, Any]] = [{"code": x["code"], + "name_l10n": x["name"], + "created_by_id": creator_pk, + "updated_by_id": creator_pk} + for x in to_add] locales: list[str] = [x[5:] for x in to_add[0] if x.startswith("l10n-")] l10n_data: list[dict[str, str]] = [{"currency_code": x["code"], "locale": y, diff --git a/src/accounting/journal_entry/forms/journal_entry.py b/src/accounting/journal_entry/forms/journal_entry.py index 67df10f..c75fc92 100644 --- a/src/accounting/journal_entry/forms/journal_entry.py +++ b/src/accounting/journal_entry/forms/journal_entry.py @@ -18,8 +18,8 @@ """ import datetime as dt -import typing as t from abc import ABC, abstractmethod +from typing import TypeVar, Generic, Type import sqlalchemy as sa from flask_babel import LazyString @@ -29,13 +29,13 @@ from wtforms import DateField, FieldList, FormField, TextAreaField, \ from wtforms.validators import DataRequired, ValidationError from accounting import db +from accounting.journal_entry.utils.account_option import AccountOption +from accounting.journal_entry.utils.description_editor import DescriptionEditor +from accounting.journal_entry.utils.original_line_items import \ + get_selectable_original_line_items from accounting.locale import lazy_gettext from accounting.models import JournalEntry, Account, JournalEntryLineItem, \ JournalEntryCurrency -from accounting.journal_entry.utils.account_option import AccountOption -from accounting.journal_entry.utils.original_line_items import \ - get_selectable_original_line_items -from accounting.journal_entry.utils.description_editor import DescriptionEditor from accounting.utils.random_id import new_id from accounting.utils.strip_text import strip_multiline_text from accounting.utils.user import get_current_user_pk @@ -123,7 +123,7 @@ class JournalEntryForm(FlaskForm): super().__init__(*args, **kwargs) self.is_modified: bool = False """Whether the journal entry is modified during populate_obj().""" - self.collector: t.Type[LineItemCollector] = LineItemCollector + self.collector: Type[LineItemCollector] = LineItemCollector """The line item collector. The default is the base abstract collector only to provide the correct type. The subclass forms should provide their own collectors.""" @@ -155,7 +155,7 @@ class JournalEntryForm(FlaskForm): self.__set_date(obj, self.date.data) obj.note = self.note.data - collector_cls: t.Type[LineItemCollector] = self.collector + collector_cls: Type[LineItemCollector] = self.collector collector: collector_cls = collector_cls(self, obj) collector.collect() @@ -309,11 +309,11 @@ class JournalEntryForm(FlaskForm): return db.session.scalar(select) -T = t.TypeVar("T", bound=JournalEntryForm) +T = TypeVar("T", bound=JournalEntryForm) """A journal entry form variant.""" -class LineItemCollector(t.Generic[T], ABC): +class LineItemCollector(Generic[T], ABC): """The line item collector.""" def __init__(self, form: T, obj: JournalEntry): diff --git a/src/accounting/journal_entry/utils/description_editor.py b/src/accounting/journal_entry/utils/description_editor.py index 193bfaf..b3ad773 100644 --- a/src/accounting/journal_entry/utils/description_editor.py +++ b/src/accounting/journal_entry/utils/description_editor.py @@ -18,7 +18,7 @@ """ import re -import typing as t +from typing import Literal import sqlalchemy as sa @@ -124,12 +124,12 @@ class DescriptionTag: class DescriptionType: """A description type""" - def __init__(self, type_id: t.Literal["general", "travel", "bus"]): + def __init__(self, type_id: Literal["general", "travel", "bus"]): """Constructs a description type. :param type_id: The type ID, either "general", "travel", or "bus". """ - self.id: t.Literal["general", "travel", "bus"] = type_id + self.id: Literal["general", "travel", "bus"] = type_id """The type ID.""" self.__tag_dict: dict[str, DescriptionTag] = {} """A dictionary from the tag name to their corresponding tag.""" @@ -181,12 +181,12 @@ class DescriptionRecurring: class DescriptionDebitCredit: """The description on debit or credit.""" - def __init__(self, debit_credit: t.Literal["debit", "credit"]): + def __init__(self, debit_credit: Literal["debit", "credit"]): """Constructs the description on debit or credit. :param debit_credit: Either "debit" or "credit". """ - self.debit_credit: t.Literal["debit", "credit"] = debit_credit + self.debit_credit: Literal["debit", "credit"] = debit_credit """Either debit or credit.""" self.general: DescriptionType = DescriptionType("general") """The general tags.""" @@ -194,14 +194,14 @@ class DescriptionDebitCredit: """The travel tags.""" self.bus: DescriptionType = DescriptionType("bus") """The bus tags.""" - self.__type_dict: dict[t.Literal["general", "travel", "bus"], + self.__type_dict: dict[Literal["general", "travel", "bus"], DescriptionType] \ = {x.id: x for x in {self.general, self.travel, self.bus}} """A dictionary from the type ID to the corresponding tags.""" self.recurring: list[DescriptionRecurring] = [] """The recurring transactions.""" - def add_tag(self, tag_type: t.Literal["general", "travel", "bus"], + def add_tag(self, tag_type: Literal["general", "travel", "bus"], name: str, account: Account, freq: int) -> None: """Adds a tag. @@ -278,7 +278,7 @@ class DescriptionEditor: accounts: dict[int, Account] \ = {x.id: x for x in Account.query .filter(Account.id.in_({x.account_id for x in result})).all()} - debit_credit_dict: dict[t.Literal["debit", "credit"], + debit_credit_dict: dict[Literal["debit", "credit"], DescriptionDebitCredit] \ = {x.debit_credit: x for x in {self.debit, self.credit}} for row in result: diff --git a/src/accounting/journal_entry/utils/operators.py b/src/accounting/journal_entry/utils/operators.py index e4e8c35..8915e36 100644 --- a/src/accounting/journal_entry/utils/operators.py +++ b/src/accounting/journal_entry/utils/operators.py @@ -17,19 +17,19 @@ """The operators for different journal entry types. """ -import typing as t from abc import ABC, abstractmethod +from typing import Type from flask import render_template, request, abort from flask_wtf import FlaskForm -from accounting.models import JournalEntry -from accounting.template_globals import default_currency_code -from accounting.utils.journal_entry_types import JournalEntryType from accounting.journal_entry.forms import JournalEntryForm, \ CashReceiptJournalEntryForm, CashDisbursementJournalEntryForm, \ TransferJournalEntryForm from accounting.journal_entry.forms.line_item import LineItemForm +from accounting.models import JournalEntry +from accounting.template_globals import default_currency_code +from accounting.utils.journal_entry_types import JournalEntryType class JournalEntryOperator(ABC): @@ -39,7 +39,7 @@ class JournalEntryOperator(ABC): @property @abstractmethod - def form(self) -> t.Type[JournalEntryForm]: + def form(self) -> Type[JournalEntryForm]: """Returns the form class. :return: The form class. @@ -100,7 +100,7 @@ class CashReceiptJournalEntry(JournalEntryOperator): """The order when checking the journal entry operator.""" @property - def form(self) -> t.Type[JournalEntryForm]: + def form(self) -> Type[JournalEntryForm]: """Returns the form class. :return: The form class. @@ -170,7 +170,7 @@ class CashDisbursementJournalEntry(JournalEntryOperator): """The order when checking the journal entry operator.""" @property - def form(self) -> t.Type[JournalEntryForm]: + def form(self) -> Type[JournalEntryForm]: """Returns the form class. :return: The form class. @@ -243,7 +243,7 @@ class TransferJournalEntry(JournalEntryOperator): """The order when checking the journal entry operator.""" @property - def form(self) -> t.Type[JournalEntryForm]: + def form(self) -> Type[JournalEntryForm]: """Returns the form class. :return: The form class. diff --git a/src/accounting/models.py b/src/accounting/models.py index 73da749..70892ff 100644 --- a/src/accounting/models.py +++ b/src/accounting/models.py @@ -21,8 +21,8 @@ from __future__ import annotations import datetime as dt import re -import typing as t from decimal import Decimal +from typing import Type, Annotated, Self import sqlalchemy as sa from babel import Locale @@ -34,16 +34,16 @@ from accounting import db from accounting.locale import gettext from accounting.utils.user import user_cls, user_pk_column -timestamp: t.Type[dt.datetime] \ - = t.Annotated[dt.datetime, mapped_column(db.DateTime(timezone=True), - server_default=db.func.now())] +timestamp: Type[dt.datetime] \ + = Annotated[dt.datetime, mapped_column(db.DateTime(timezone=True), + server_default=db.func.now())] """The timestamp.""" -user_pk: t.Type[int] \ - = t.Annotated[int, mapped_column(db.ForeignKey(user_pk_column, - onupdate="CASCADE"))] +user_pk: Type[int] \ + = Annotated[int, mapped_column(db.ForeignKey(user_pk_column, + onupdate="CASCADE"))] """The user primary key.""" -random_pk: t.Type[int] \ - = t.Annotated[int, mapped_column(primary_key=True, autoincrement=False)] +random_pk: Type[int] \ + = Annotated[int, mapped_column(primary_key=True, autoincrement=False)] """The random primary key.""" @@ -273,11 +273,11 @@ class Account(db.Model): :return: None. """ AccountL10n.query.filter(AccountL10n.account == self).delete() - cls: t.Type[t.Self] = self.__class__ + cls: Type[Self] = self.__class__ cls.query.filter(cls.id == self.id).delete() @classmethod - def find_by_code(cls, code: str) -> t.Self | None: + def find_by_code(cls, code: str) -> Self | None: """Finds an account by its code. :param code: The code. @@ -290,7 +290,7 @@ class Account(db.Model): cls.no == int(m.group(2))).first() @classmethod - def selectable_debit(cls) -> list[t.Self]: + def selectable_debit(cls) -> list[Self]: """Returns the selectable debit accounts. Payable line items can not start from debit. @@ -313,7 +313,7 @@ class Account(db.Model): .order_by(cls.base_code, cls.no).all() @classmethod - def selectable_credit(cls) -> list[t.Self]: + def selectable_credit(cls) -> list[Self]: """Returns the selectable debit accounts. Receivable line items can not start from credit. @@ -335,7 +335,7 @@ class Account(db.Model): .order_by(cls.base_code, cls.no).all() @classmethod - def cash(cls) -> t.Self: + def cash(cls) -> Self: """Returns the cash account. :return: The cash account @@ -343,7 +343,7 @@ class Account(db.Model): return cls.find_by_code(cls.CASH_CODE) @classmethod - def accumulated_change(cls) -> t.Self: + def accumulated_change(cls) -> Self: """Returns the accumulated-change account. :return: The accumulated-change account @@ -467,7 +467,7 @@ class Currency(db.Model): :return: None. """ CurrencyL10n.query.filter(CurrencyL10n.currency == self).delete() - cls: t.Type[t.Self] = self.__class__ + cls: Type[Self] = self.__class__ cls.query.filter(cls.code == self.code).delete() @@ -797,14 +797,14 @@ class JournalEntryLineItem(db.Model): setattr(self, "__balance", value) @property - def offsets(self) -> list[t.Self]: + def offsets(self) -> list[Self]: """Returns the offset items. :return: The offset items. """ if not hasattr(self, "__offsets"): - cls: t.Type[t.Self] = self.__class__ - offsets: list[t.Self] = cls.query.join(JournalEntry)\ + cls: Type[Self] = self.__class__ + offsets: list[Self] = cls.query.join(JournalEntry)\ .filter(JournalEntryLineItem.original_line_item_id == self.id)\ .order_by(JournalEntry.date, JournalEntry.no, cls.is_debit, cls.no).all() @@ -831,7 +831,7 @@ class JournalEntryLineItem(db.Model): setattr(self, "__is_offset", value) @property - def match(self) -> t.Self | None: + def match(self) -> Self | None: """Returns the match of the line item. :return: The match of the line item. @@ -841,7 +841,7 @@ class JournalEntryLineItem(db.Model): return getattr(self, "__match") @match.setter - def match(self, value: t.Self) -> None: + def match(self, value: Self) -> None: """Sets the match of the line item. :param value: The matcho of the line item. diff --git a/src/accounting/report/period/chooser.py b/src/accounting/report/period/chooser.py index 11b43d8..4fd4f14 100644 --- a/src/accounting/report/period/chooser.py +++ b/src/accounting/report/period/chooser.py @@ -21,7 +21,7 @@ This file is largely taken from the NanoParma ERP project, first written in """ import datetime as dt -import typing as t +from collections.abc import Callable from accounting.models import JournalEntry from .period import Period @@ -32,13 +32,13 @@ from .shortcuts import ThisMonth, LastMonth, SinceLastMonth, ThisYear, \ class PeriodChooser: """The period chooser.""" - def __init__(self, get_url: t.Callable[[Period], str]): + def __init__(self, get_url: Callable[[Period], str]): """Constructs a period chooser. :param get_url: The callback to return the URL of the current report in a period. """ - self.__get_url: t.Callable[[Period], str] = get_url + self.__get_url: Callable[[Period], str] = get_url """The callback to return the URL of the current report in a period.""" # Shortcut periods diff --git a/src/accounting/report/period/parser.py b/src/accounting/report/period/parser.py index 338393e..2bfe028 100644 --- a/src/accounting/report/period/parser.py +++ b/src/accounting/report/period/parser.py @@ -20,7 +20,8 @@ import calendar import datetime as dt import re -import typing as t +from collections.abc import Callable +from typing import Type from .period import Period from .shortcuts import ThisMonth, LastMonth, SinceLastMonth, ThisYear, \ @@ -39,7 +40,7 @@ def get_period(spec: str | None = None) -> Period: """ if spec is None: return ThisMonth() - named_periods: dict[str, t.Type[t.Callable[[], Period]]] = { + named_periods: dict[str, Type[Callable[[], Period]]] = { "this-month": lambda: ThisMonth(), "last-month": lambda: LastMonth(), "since-last-month": lambda: SinceLastMonth(), diff --git a/src/accounting/report/period/period.py b/src/accounting/report/period/period.py index a8dcbab..2ac899f 100644 --- a/src/accounting/report/period/period.py +++ b/src/accounting/report/period/period.py @@ -21,7 +21,7 @@ This file is largely taken from the NanoParma ERP project, first written in """ import datetime as dt -import typing as t +from typing import Self from .description import get_desc from .month_end import month_end @@ -119,7 +119,7 @@ class Period: and not self.is_a_day @property - def before(self) -> t.Self | None: + def before(self) -> Self | None: """Returns the period before this period. :return: The period before this period. diff --git a/src/accounting/report/utils/base_page_params.py b/src/accounting/report/utils/base_page_params.py index 6b608c4..f60766b 100644 --- a/src/accounting/report/utils/base_page_params.py +++ b/src/accounting/report/utils/base_page_params.py @@ -17,8 +17,9 @@ """The page parameters of a report. """ -import typing as t from abc import ABC, abstractmethod +from collections.abc import Callable +from typing import Type from urllib.parse import urlparse, ParseResult, parse_qsl, urlencode, \ urlunparse @@ -52,7 +53,7 @@ class BasePageParams(ABC): """ @property - def journal_entry_types(self) -> t.Type[JournalEntryType]: + def journal_entry_types(self) -> Type[JournalEntryType]: """Returns the journal entry types. :return: The journal entry types. @@ -72,7 +73,7 @@ class BasePageParams(ABC): return urlunparse(parts) @staticmethod - def _get_currency_options(get_url: t.Callable[[Currency], str], + def _get_currency_options(get_url: Callable[[Currency], str], active_currency: Currency) -> list[OptionLink]: """Returns the currency options. diff --git a/src/accounting/report/utils/report_chooser.py b/src/accounting/report/utils/report_chooser.py index f79420e..57da226 100644 --- a/src/accounting/report/utils/report_chooser.py +++ b/src/accounting/report/utils/report_chooser.py @@ -21,7 +21,7 @@ This file is largely taken from the NanoParma ERP project, first written in """ import re -import typing as t +from collections.abc import Iterator from flask_babel import LazyString @@ -190,7 +190,7 @@ class ReportChooser: self.__active_report == ReportType.UNMATCHED, fa_icon="fa-solid fa-file-circle-question") - def __iter__(self) -> t.Iterator[OptionLink]: + def __iter__(self) -> Iterator[OptionLink]: """Returns the iteration of the reports. :return: The iteration of the reports. diff --git a/src/accounting/template_filters.py b/src/accounting/template_filters.py index 7d93f23..8005911 100644 --- a/src/accounting/template_filters.py +++ b/src/accounting/template_filters.py @@ -18,8 +18,8 @@ """ import datetime as dt -import typing as t from decimal import Decimal +from typing import Any from flask_babel import get_locale @@ -71,7 +71,7 @@ def format_date(value: dt.date) -> str: return "{}/{}({})".format(value.month, value.day, weekday) -def default(value: t.Any, default_value: t.Any = "") -> t.Any: +def default(value: Any, default_value: Any = "") -> Any: """Returns the default value if the given value is None. :param value: The value. diff --git a/src/accounting/utils/cast.py b/src/accounting/utils/cast.py index 6317888..9ebfa0a 100644 --- a/src/accounting/utils/cast.py +++ b/src/accounting/utils/cast.py @@ -20,10 +20,10 @@ warnings from the IDE. This module should not import any other module from the application. """ -import typing as t +from typing import Any -def s(message: t.Any) -> str: +def s(message: Any) -> str: """Casts the LazyString message to the string type. :param message: The message. diff --git a/src/accounting/utils/current_account.py b/src/accounting/utils/current_account.py index ff5bd09..25b0245 100644 --- a/src/accounting/utils/current_account.py +++ b/src/accounting/utils/current_account.py @@ -17,12 +17,13 @@ """The current assets and liabilities account. """ -import typing as t +from typing import Self + +import sqlalchemy as sa from accounting import db from accounting.locale import gettext from accounting.models import Account -import sqlalchemy as sa class CurrentAccount: @@ -54,7 +55,7 @@ class CurrentAccount: return self.str @classmethod - def current_assets_and_liabilities(cls) -> t.Self: + def current_assets_and_liabilities(cls) -> Self: """Returns the pseudo account for all current assets and liabilities. :return: The pseudo account for all current assets and liabilities. @@ -67,7 +68,7 @@ class CurrentAccount: return account @classmethod - def accounts(cls) -> list[t.Self]: + def accounts(cls) -> list[Self]: """Returns the current assets and liabilities accounts. :return: The current assets and liabilities accounts. diff --git a/src/accounting/utils/flash_errors.py b/src/accounting/utils/flash_errors.py index 527e157..ba5a849 100644 --- a/src/accounting/utils/flash_errors.py +++ b/src/accounting/utils/flash_errors.py @@ -19,7 +19,7 @@ This module should not import any other module from the application. """ -import typing as t +from typing import Any from flask import flash from flask_wtf import FlaskForm @@ -34,7 +34,7 @@ def flash_form_errors(form: FlaskForm) -> None: __flash_errors(form.errors) -def __flash_errors(error: t.Any) -> None: +def __flash_errors(error: Any) -> None: """Flash all errors recursively. :param error: The errors. diff --git a/src/accounting/utils/offset_alias.py b/src/accounting/utils/offset_alias.py index 958cce0..bd32225 100644 --- a/src/accounting/utils/offset_alias.py +++ b/src/accounting/utils/offset_alias.py @@ -17,7 +17,7 @@ """The SQLAlchemy alias for the offset items. """ -import typing as t +from typing import Any import sqlalchemy as sa @@ -30,10 +30,10 @@ def offset_alias() -> sa.Alias: :return: The SQLAlchemy alias for the offset items. """ - def as_from(model_cls: t.Any) -> sa.FromClause: + def as_from(model_cls: Any) -> sa.FromClause: return model_cls - def as_alias(alias: t.Any) -> sa.Alias: + def as_alias(alias: Any) -> sa.Alias: return alias return as_alias(sa.alias(as_from(JournalEntryLineItem), name="offset")) diff --git a/src/accounting/utils/pagination.py b/src/accounting/utils/pagination.py index f2bf082..1dc375a 100644 --- a/src/accounting/utils/pagination.py +++ b/src/accounting/utils/pagination.py @@ -19,7 +19,7 @@ This module should not import any other module from the application. """ -import typing as t +from typing import TypeVar, Generic from urllib.parse import urlparse, parse_qsl, urlencode, urlunparse, \ ParseResult @@ -62,10 +62,10 @@ class Redirection(RequestRedirect): DEFAULT_PAGE_SIZE: int = 10 """The default page size.""" -T = t.TypeVar("T") +T = TypeVar("T") -class Pagination(t.Generic[T]): +class Pagination(Generic[T]): """The pagination utility.""" def __init__(self, items: list[T], is_reversed: bool = False): @@ -91,7 +91,7 @@ class Pagination(t.Generic[T]): """The options to the number of items in a page.""" -class AbstractPagination(t.Generic[T]): +class AbstractPagination(Generic[T]): """An abstract pagination.""" def __init__(self): diff --git a/src/accounting/utils/permission.py b/src/accounting/utils/permission.py index 459b6b6..d8f17e2 100644 --- a/src/accounting/utils/permission.py +++ b/src/accounting/utils/permission.py @@ -19,21 +19,21 @@ This module should not import any other module from the application. """ -import typing as t +from collections.abc import Callable from flask import abort, Blueprint, Response from accounting.utils.user import get_current_user, UserUtilityInterface -def has_permission(rule: t.Callable[[], bool]) -> t.Callable: +def has_permission(rule: Callable[[], bool]) -> Callable: """The permission decorator to check whether the current user is allowed. :param rule: The permission rule. :return: The view decorator. """ - def decorator(view: t.Callable) -> t.Callable: + def decorator(view: Callable) -> Callable: """The view decorator to decorate a view with permission tests. :param view: The view. @@ -61,16 +61,16 @@ def has_permission(rule: t.Callable[[], bool]) -> t.Callable: return decorator -__can_view_func: t.Callable[[], bool] = lambda: True +__can_view_func: Callable[[], bool] = lambda: True """The callback that returns whether the current user can view the accounting data.""" -__can_edit_func: t.Callable[[], bool] = lambda: True +__can_edit_func: Callable[[], bool] = lambda: True """The callback that returns whether the current user can edit the accounting data.""" -__can_admin_func: t.Callable[[], bool] = lambda: True +__can_admin_func: Callable[[], bool] = lambda: True """The callback that returns whether the current user can administrate the accounting settings.""" -_unauthorized_func: t.Callable[[], Response | None] \ +_unauthorized_func: Callable[[], Response | None] \ = lambda: Response(status=403) """The callback that returns the response to require the user to log in.""" diff --git a/src/accounting/utils/random_id.py b/src/accounting/utils/random_id.py index e3c2800..817583a 100644 --- a/src/accounting/utils/random_id.py +++ b/src/accounting/utils/random_id.py @@ -19,13 +19,13 @@ This module should not import any other module from the application. """ -import typing as t from secrets import randbelow +from typing import Type from accounting import db -def new_id(cls: t.Type): +def new_id(cls: Type): """Returns a new random ID for the data model. :param cls: The data model. diff --git a/src/accounting/utils/user.py b/src/accounting/utils/user.py index d879ac4..b5d8583 100644 --- a/src/accounting/utils/user.py +++ b/src/accounting/utils/user.py @@ -19,17 +19,17 @@ This module should not import any other module from the application. """ -import typing as t from abc import ABC, abstractmethod +from typing import TypeVar, Generic, Type import sqlalchemy as sa from flask import g, Response from flask_sqlalchemy.model import Model -T = t.TypeVar("T", bound=Model) +T = TypeVar("T", bound=Model) -class UserUtilityInterface(t.Generic[T], ABC): +class UserUtilityInterface(Generic[T], ABC): """The interface for the user utilities.""" @abstractmethod @@ -72,7 +72,7 @@ class UserUtilityInterface(t.Generic[T], ABC): @property @abstractmethod - def cls(self) -> t.Type[T]: + def cls(self) -> Type[T]: """Returns the class of the user data model. :return: The class of the user data model. @@ -112,7 +112,7 @@ class UserUtilityInterface(t.Generic[T], ABC): __user_utils: UserUtilityInterface """The user utilities.""" -user_cls: t.Type[Model] = Model +user_cls: Type[Model] = Model """The user class.""" user_pk_column: sa.Column = sa.Column(sa.Integer) """The primary key column of the user class.""" diff --git a/tests/test_commands.py b/tests/test_commands.py index 74ee287..bf32fbe 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -18,8 +18,8 @@ """ import csv -import typing as t import unittest +from typing import Any import sqlalchemy as sa from click.testing import Result @@ -80,7 +80,7 @@ class ConsoleCommandTestCase(unittest.TestCase): from accounting.models import BaseAccount with open(data_dir / "base_accounts.csv") as fp: - data: dict[dict[str, t.Any]] \ + data: dict[dict[str, Any]] \ = {x["code"]: {"code": x["code"], "title": x["title"], "l10n": {y[5:]: x[y] @@ -135,7 +135,7 @@ class ConsoleCommandTestCase(unittest.TestCase): from accounting.models import Currency with open(data_dir / "currencies.csv") as fp: - data: dict[dict[str, t.Any]] \ + data: dict[dict[str, Any]] \ = {x["code"]: {"code": x["code"], "name": x["name"], "l10n": {y[5:]: x[y] diff --git a/tests/test_site/__init__.py b/tests/test_site/__init__.py index 2ab46fb..fb1ec15 100644 --- a/tests/test_site/__init__.py +++ b/tests/test_site/__init__.py @@ -18,8 +18,8 @@ """ import os -import typing as t from secrets import token_urlsafe +from typing import Type from click.testing import Result from flask import Flask, Blueprint, render_template, redirect, Response, \ @@ -94,7 +94,7 @@ def create_app(is_testing: bool = False) -> Flask: return redirect(append_next(url_for("auth.login-form"))) @property - def cls(self) -> t.Type[auth.User]: + def cls(self) -> Type[auth.User]: return auth.User @property diff --git a/tests/test_site/auth.py b/tests/test_site/auth.py index 269f00a..d661066 100644 --- a/tests/test_site/auth.py +++ b/tests/test_site/auth.py @@ -17,7 +17,7 @@ """The authentication for the Mia! Accounting demonstration website. """ -import typing as t +from collections.abc import Callable from flask import Blueprint, render_template, Flask, redirect, url_for, \ session, request, g, Response, abort @@ -96,7 +96,7 @@ def current_user() -> User | None: return g.user -def admin_required(view: t.Callable) -> t.Callable: +def admin_required(view: Callable) -> Callable: """The view decorator to require the user to be an administrator. :param view: The view. diff --git a/tests/test_site/lib.py b/tests/test_site/lib.py index 8e6fbdb..7e27aba 100644 --- a/tests/test_site/lib.py +++ b/tests/test_site/lib.py @@ -20,10 +20,10 @@ from __future__ import annotations import datetime as dt -import typing as t from abc import ABC, abstractmethod from decimal import Decimal from secrets import randbelow +from typing import Any import sqlalchemy as sa from flask import Flask @@ -193,8 +193,8 @@ class BaseTestData(ABC): .filter(User.username == username).first() assert current_user is not None self.__current_user_id: int = current_user.id - self.__journal_entries: list[dict[str, t.Any]] = [] - self.__line_items: list[dict[str, t.Any]] = [] + self.__journal_entries: list[dict[str, Any]] = [] + self.__line_items: list[dict[str, Any]] = [] self._init_data() @abstractmethod @@ -258,7 +258,7 @@ class BaseTestData(ABC): assert account is not None debit_no = debit_no + 1 line_item.id = self.__new_id(existing_l_id) - data: dict[str, t.Any] \ + data: dict[str, Any] \ = {"id": line_item.id, "journal_entry_id": journal_entry_data.id, "is_debit": True, @@ -277,7 +277,7 @@ class BaseTestData(ABC): assert account is not None credit_no = credit_no + 1 line_item.id = self.__new_id(existing_l_id) - data: dict[str, t.Any] \ + data: dict[str, Any] \ = {"id": line_item.id, "journal_entry_id": journal_entry_data.id, "is_debit": False, diff --git a/tests/testlib.py b/tests/testlib.py index d8f9057..883cd2b 100644 --- a/tests/testlib.py +++ b/tests/testlib.py @@ -20,7 +20,7 @@ from __future__ import annotations import re -import typing as t +from typing import Literal import httpx from flask import Flask, render_template_string @@ -108,7 +108,7 @@ def get_client(app: Flask, username: str) -> tuple[httpx.Client, str]: def set_locale(client: httpx.Client, csrf_token: str, - locale: t.Literal["en", "zh_Hant", "zh_Hans"]) -> None: + locale: Literal["en", "zh_Hant", "zh_Hans"]) -> None: """Sets the current locale. :param client: The test client.