From cda9e4e3c6db5f7c6995bd8fb8cb97c13564a215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BE=9D=E7=91=AA=E8=B2=93?= Date: Wed, 26 Apr 2023 18:22:45 +0800 Subject: [PATCH] Replaced importing the "typing" module as "t" with importing the individual names in the "typing" module. Since Python 3.9 introduced type hinting generics in standard collections, we do not have as many names to import now. This is also to be consistent with the practices of most major and standard packages and examples. --- src/accounting/account/commands.py | 8 ++-- src/accounting/currency/commands.py | 12 +++--- .../journal_entry/forms/journal_entry.py | 18 ++++---- .../journal_entry/utils/description_editor.py | 16 +++---- .../journal_entry/utils/operators.py | 16 +++---- src/accounting/models.py | 42 +++++++++---------- src/accounting/report/period/chooser.py | 6 +-- src/accounting/report/period/parser.py | 5 ++- src/accounting/report/period/period.py | 4 +- .../report/utils/base_page_params.py | 7 ++-- src/accounting/report/utils/report_chooser.py | 4 +- src/accounting/template_filters.py | 4 +- src/accounting/utils/cast.py | 4 +- src/accounting/utils/current_account.py | 9 ++-- src/accounting/utils/flash_errors.py | 4 +- src/accounting/utils/offset_alias.py | 6 +-- src/accounting/utils/pagination.py | 8 ++-- src/accounting/utils/permission.py | 14 +++---- src/accounting/utils/random_id.py | 4 +- src/accounting/utils/user.py | 10 ++--- tests/test_commands.py | 6 +-- tests/test_site/__init__.py | 4 +- tests/test_site/auth.py | 4 +- tests/test_site/lib.py | 10 ++--- tests/testlib.py | 4 +- 25 files changed, 116 insertions(+), 113 deletions(-) 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.