Adds type checking for templatetags

This commit is contained in:
Mouse Reeve 2024-08-27 17:34:16 -07:00
parent ef30c7cf65
commit 676a6b4b7f
18 changed files with 93 additions and 68 deletions

View file

@ -1,8 +1,9 @@
""" database schema for books and shelves """ """ database schema for books and shelves """
from __future__ import annotations
from itertools import chain from itertools import chain
import re import re
from typing import Any, Dict, Optional, Iterable from typing import Any, Dict, Optional, Iterable, TYPE_CHECKING
from typing_extensions import Self from typing_extensions import Self
from django.contrib.postgres.search import SearchVectorField from django.contrib.postgres.search import SearchVectorField
@ -33,6 +34,9 @@ from .activitypub_mixin import OrderedCollectionPageMixin, ObjectMixin
from .base_model import BookWyrmModel from .base_model import BookWyrmModel
from . import fields from . import fields
if TYPE_CHECKING:
from bookwyrm.models import Author
class BookDataModel(ObjectMixin, BookWyrmModel): class BookDataModel(ObjectMixin, BookWyrmModel):
"""fields shared between editable book data (books, works, authors)""" """fields shared between editable book data (books, works, authors)"""
@ -415,7 +419,7 @@ class Work(OrderedCollectionPageMixin, Book):
"""in case the default edition is not set""" """in case the default edition is not set"""
return self.editions.order_by("-edition_rank").first() return self.editions.order_by("-edition_rank").first()
def author_edition(self, author): def author_edition(self, author: Author) -> Any:
"""in case the default edition doesn't have the required author""" """in case the default edition doesn't have the required author"""
return self.editions.filter(authors=author).order_by("-edition_rank").first() return self.editions.filter(authors=author).order_by("-edition_rank").first()

View file

@ -1,5 +1,7 @@
""" template filters """ """ template filters """
from typing import Any
from django import template from django import template
from django.db.models import QuerySet
from bookwyrm import models from bookwyrm import models
@ -7,13 +9,13 @@ register = template.Library()
@register.filter(name="review_count") @register.filter(name="review_count")
def get_review_count(book): def get_review_count(book: models.Edition) -> int:
"""how many reviews?""" """how many reviews?"""
return models.Review.objects.filter(deleted=False, book=book).count() return models.Review.objects.filter(deleted=False, book=book).count()
@register.filter(name="book_description") @register.filter(name="book_description")
def get_book_description(book): def get_book_description(book: models.Edition) -> Any:
"""use the work's text if the book doesn't have it""" """use the work's text if the book doesn't have it"""
if book.description: if book.description:
return book.description return book.description
@ -24,12 +26,12 @@ def get_book_description(book):
@register.simple_tag(takes_context=False) @register.simple_tag(takes_context=False)
def get_book_file_links(book): def get_book_file_links(book: models.Edition) -> QuerySet[models.FileLink]:
"""links for a book""" """links for a book"""
return book.file_links.filter(domain__status="approved") return book.file_links.filter(domain__status="approved")
@register.filter(name="author_edition") @register.filter(name="author_edition")
def get_author_edition(book, author): def get_author_edition(book: models.Work, author: models.Author) -> Any:
"""default edition for a book on the author page""" """default edition for a book on the author page"""
return book.author_edition(author) return book.author_edition(author)

View file

@ -7,18 +7,18 @@ register = template.Library()
@register.filter(name="uptime") @register.filter(name="uptime")
def uptime(seconds): def uptime(seconds: float) -> str:
"""Seconds uptime to a readable format""" """Seconds uptime to a readable format"""
return str(datetime.timedelta(seconds=seconds)) return str(datetime.timedelta(seconds=seconds))
@register.filter(name="runtime") @register.filter(name="runtime")
def runtime(timestamp): def runtime(timestamp: float) -> datetime.timedelta:
"""How long has it been?""" """How long has it been?"""
return datetime.datetime.now() - datetime.datetime.fromtimestamp(timestamp) return datetime.datetime.now() - datetime.datetime.fromtimestamp(timestamp)
@register.filter(name="shortname") @register.filter(name="shortname")
def shortname(name): def shortname(name: str) -> str:
"""removes bookwyrm.celery...""" """removes bookwyrm.celery..."""
return ".".join(name.split(".")[-2:]) return ".".join(name.split(".")[-2:])

View file

@ -1,4 +1,6 @@
""" additional formatting of dates """ """ additional formatting of dates """
from typing import Any
from django import template from django import template
from django.template import defaultfilters from django.template import defaultfilters
from django.contrib.humanize.templatetags.humanize import naturalday from django.contrib.humanize.templatetags.humanize import naturalday
@ -9,7 +11,7 @@ register = template.Library()
@register.filter(expects_localtime=True) @register.filter(expects_localtime=True)
def naturalday_partial(date, arg=None): def naturalday_partial(date: Any, arg: Any = None) -> str | None:
"""chooses appropriate precision if date is a PartialDate object """chooses appropriate precision if date is a PartialDate object
If arg is a Django-defined format such as "DATE_FORMAT", it will be adjusted If arg is a Django-defined format such as "DATE_FORMAT", it will be adjusted

View file

@ -1,13 +1,15 @@
""" tags used on the feed pages """ """ tags used on the feed pages """
from typing import Any
from django import template from django import template
from bookwyrm.views.feed import get_suggested_books from bookwyrm.views.feed import get_suggested_books
from bookwyrm import models
register = template.Library() register = template.Library()
@register.filter(name="load_subclass") @register.filter(name="load_subclass")
def load_subclass(status): def load_subclass(status: models.Status) -> models.Status:
"""sometimes you didn't select_subclass""" """sometimes you didn't select_subclass"""
if hasattr(status, "quotation"): if hasattr(status, "quotation"):
return status.quotation return status.quotation
@ -21,7 +23,7 @@ def load_subclass(status):
@register.simple_tag(takes_context=True) @register.simple_tag(takes_context=True)
def suggested_books(context): def suggested_books(context: dict[str, Any]) -> Any:
"""get books for suggested books panel""" """get books for suggested books panel"""
# this happens here instead of in the view so that the template snippet can # this happens here instead of in the view so that the template snippet can
# be cached in the template # be cached in the template

View file

@ -7,21 +7,21 @@ register = template.Library()
@register.filter(name="has_groups") @register.filter(name="has_groups")
def has_groups(user): def has_groups(user: models.User) -> bool:
"""whether or not the user has a pending invitation to join this group""" """whether or not the user has a pending invitation to join this group"""
return models.GroupMember.objects.filter(user=user).exists() return models.GroupMember.objects.filter(user=user).exists()
@register.filter(name="is_member") @register.filter(name="is_member")
def is_member(group, user): def is_member(group: models.Group, user: models.User) -> bool:
"""whether or not the user is a member of this group""" """whether or not the user is a member of this group"""
return models.GroupMember.objects.filter(group=group, user=user).exists() return models.GroupMember.objects.filter(group=group, user=user).exists()
@register.filter(name="is_invited") @register.filter(name="is_invited")
def is_invited(group, user): def is_invited(group: models.Group, user: models.User) -> bool:
"""whether or not the user has a pending invitation to join this group""" """whether or not the user has a pending invitation to join this group"""
return models.GroupMemberInvitation.objects.filter(group=group, user=user).exists() return models.GroupMemberInvitation.objects.filter(group=group, user=user).exists()

View file

@ -1,4 +1,5 @@
""" template filters for status interaction buttons """ """ template filters for status interaction buttons """
from typing import Any
from django import template from django import template
from bookwyrm import models from bookwyrm import models
@ -9,7 +10,7 @@ register = template.Library()
@register.filter(name="liked") @register.filter(name="liked")
def get_user_liked(user, status): def get_user_liked(user: models.User, status: models.Status) -> Any:
"""did the given user fav a status?""" """did the given user fav a status?"""
return get_or_set( return get_or_set(
f"fav-{user.id}-{status.id}", f"fav-{user.id}-{status.id}",
@ -21,7 +22,7 @@ def get_user_liked(user, status):
@register.filter(name="boosted") @register.filter(name="boosted")
def get_user_boosted(user, status): def get_user_boosted(user: models.User, status: models.Status) -> Any:
"""did the given user fav a status?""" """did the given user fav a status?"""
return get_or_set( return get_or_set(
f"boost-{user.id}-{status.id}", f"boost-{user.id}-{status.id}",
@ -32,13 +33,13 @@ def get_user_boosted(user, status):
@register.filter(name="saved") @register.filter(name="saved")
def get_user_saved_lists(user, book_list): def get_user_saved_lists(user: models.User, book_list: models.List) -> bool:
"""did the user save a list""" """did the user save a list"""
return user.saved_lists.filter(id=book_list.id).exists() return user.saved_lists.filter(id=book_list.id).exists()
@register.simple_tag(takes_context=True) @register.simple_tag(takes_context=True)
def get_relationship(context, user_object): def get_relationship(context: dict[str, Any], user_object: models.User) -> Any:
"""caches the relationship between the logged in user and another user""" """caches the relationship between the logged in user and another user"""
user = context["request"].user user = context["request"].user
return get_or_set( return get_or_set(
@ -50,7 +51,9 @@ def get_relationship(context, user_object):
) )
def get_relationship_name(user, user_object): def get_relationship_name(
user: models.User, user_object: models.User
) -> dict[str, bool]:
"""returns the relationship type""" """returns the relationship type"""
types = { types = {
"is_following": False, "is_following": False,

View file

@ -1,6 +1,7 @@
""" template filters """ """ template filters """
from typing import Optional
from django import template from django import template
from django.db.models import Avg, StdDev, Count, F, Q from django.db.models import Avg, StdDev, Count, F, Q, QuerySet
from bookwyrm import models from bookwyrm import models
@ -8,7 +9,7 @@ register = template.Library()
@register.simple_tag(takes_context=False) @register.simple_tag(takes_context=False)
def get_book_superlatives(): def get_book_superlatives() -> dict[str, Optional[models.Work]]:
"""get book stats for the about page""" """get book stats for the about page"""
total_ratings = models.Review.objects.filter(local=True, deleted=False).count() total_ratings = models.Review.objects.filter(local=True, deleted=False).count()
data = {} data = {}
@ -67,7 +68,7 @@ def get_book_superlatives():
@register.simple_tag(takes_context=False) @register.simple_tag(takes_context=False)
def get_landing_books(): def get_landing_books() -> list[QuerySet[models.Edition]]:
"""list of books for the landing page""" """list of books for the landing page"""
return list( return list(
set( set(

View file

@ -5,7 +5,7 @@ register = template.Library()
@register.simple_tag(takes_context=False) @register.simple_tag(takes_context=False)
def get_lang(): def get_lang() -> str:
"""get current language, strip to the first two letters""" """get current language, strip to the first two letters"""
language = utils.translation.get_language() language = utils.translation.get_language()
return language[0 : language.find("-")] return language[0 : language.find("-")]

View file

@ -1,4 +1,5 @@
""" template filters """ """ template filters """
from typing import Any
from django import template from django import template
from bookwyrm.views.status import to_markdown from bookwyrm.views.status import to_markdown
@ -7,7 +8,7 @@ register = template.Library()
@register.filter(name="to_markdown") @register.filter(name="to_markdown")
def get_markdown(content): def get_markdown(content: str) -> Any:
"""convert markdown to html""" """convert markdown to html"""
if content: if content:
return to_markdown(content) return to_markdown(content)

View file

@ -1,5 +1,7 @@
""" tags used on the feed pages """ """ tags used on the feed pages """
from typing import Optional
from django import template from django import template
from bookwyrm import models
from bookwyrm.templatetags.feed_page_tags import load_subclass from bookwyrm.templatetags.feed_page_tags import load_subclass
@ -7,7 +9,7 @@ register = template.Library()
@register.simple_tag(takes_context=False) @register.simple_tag(takes_context=False)
def related_status(notification): def related_status(notification: models.Notification) -> Optional[models.Status]:
"""for notifications""" """for notifications"""
if not notification.related_status: if not notification.related_status:
return None return None
@ -15,6 +17,6 @@ def related_status(notification):
@register.simple_tag(takes_context=False) @register.simple_tag(takes_context=False)
def get_related_users(notification): def get_related_users(notification: models.Notification) -> list[models.User]:
"""Who actually was it who liked your post""" """Who actually was it who liked your post"""
return list(reversed(list(notification.related_users.distinct())))[:10] return list(reversed(list(notification.related_users.distinct())))[:10]

View file

@ -1,4 +1,5 @@
""" template filters """ """ template filters """
from typing import Any
from django import template from django import template
from django.db.models import Avg from django.db.models import Avg
@ -10,7 +11,7 @@ register = template.Library()
@register.filter(name="rating") @register.filter(name="rating")
def get_rating(book, user): def get_rating(book: models.Edition, user: models.User) -> Any:
"""get the overall rating of a book""" """get the overall rating of a book"""
# this shouldn't happen, but it CAN # this shouldn't happen, but it CAN
if not book.parent_work: if not book.parent_work:
@ -29,7 +30,7 @@ def get_rating(book, user):
@register.filter(name="user_rating") @register.filter(name="user_rating")
def get_user_rating(book, user): def get_user_rating(book: models.Edition, user: models.User) -> Any:
"""get a user's rating of a book""" """get a user's rating of a book"""
rating = ( rating = (
models.Review.objects.filter( models.Review.objects.filter(

View file

@ -1,6 +1,8 @@
""" Filters and tags related to shelving books """ """ Filters and tags related to shelving books """
from typing import Any
from django import template from django import template
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django_stubs_ext import StrPromise
from bookwyrm import models from bookwyrm import models
from bookwyrm.utils import cache from bookwyrm.utils import cache
@ -19,7 +21,7 @@ SHELF_NAMES = {
@register.filter(name="is_book_on_shelf") @register.filter(name="is_book_on_shelf")
def get_is_book_on_shelf(book, shelf): def get_is_book_on_shelf(book: models.Edition, shelf: models.Shelf) -> Any:
"""is a book on a shelf""" """is a book on a shelf"""
return cache.get_or_set( return cache.get_or_set(
f"book-on-shelf-{book.id}-{shelf.id}", f"book-on-shelf-{book.id}-{shelf.id}",
@ -31,7 +33,7 @@ def get_is_book_on_shelf(book, shelf):
@register.filter(name="next_shelf") @register.filter(name="next_shelf")
def get_next_shelf(current_shelf): def get_next_shelf(current_shelf: str) -> str:
"""shelf you'd use to update reading progress""" """shelf you'd use to update reading progress"""
if current_shelf == "to-read": if current_shelf == "to-read":
return "reading" return "reading"
@ -45,7 +47,7 @@ def get_next_shelf(current_shelf):
@register.filter(name="translate_shelf_name") @register.filter(name="translate_shelf_name")
def get_translated_shelf_name(shelf): def get_translated_shelf_name(shelf: models.Shelf | dict[str, str]) -> str | StrPromise:
"""produce translated shelf identifiername""" """produce translated shelf identifiername"""
if not shelf: if not shelf:
return "" return ""
@ -59,7 +61,7 @@ def get_translated_shelf_name(shelf):
@register.simple_tag(takes_context=True) @register.simple_tag(takes_context=True)
def active_shelf(context, book): def active_shelf(context: dict[str, Any], book: models.Edition) -> Any:
"""check what shelf a user has a book on, if any""" """check what shelf a user has a book on, if any"""
user = context["request"].user user = context["request"].user
return cache.get_or_set( return cache.get_or_set(
@ -78,7 +80,7 @@ def active_shelf(context, book):
@register.simple_tag(takes_context=False) @register.simple_tag(takes_context=False)
def latest_read_through(book, user): def latest_read_through(book: models.Edition, user: models.User) -> Any:
"""the most recent read activity""" """the most recent read activity"""
return cache.get_or_set( return cache.get_or_set(
f"latest_read_through-{user.id}-{book.id}", f"latest_read_through-{user.id}-{book.id}",

View file

@ -6,6 +6,6 @@ register = template.Library()
@register.filter(name="half_star") @register.filter(name="half_star")
def get_half_star(value): def get_half_star(value: str) -> str:
"""one of those things that's weirdly hard with templates""" """one of those things that's weirdly hard with templates"""
return f"{value}.5" return f"{value}.5"

View file

@ -1,4 +1,5 @@
""" template filters """ """ template filters """
from typing import Any, Optional
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from django import template from django import template
from django.contrib.humanize.templatetags.humanize import naturaltime, naturalday from django.contrib.humanize.templatetags.humanize import naturaltime, naturalday
@ -12,7 +13,7 @@ register = template.Library()
@register.filter(name="mentions") @register.filter(name="mentions")
def get_mentions(status, user): def get_mentions(status: models.Status, user: models.User) -> str:
"""people to @ in a reply: the parent and all mentions""" """people to @ in a reply: the parent and all mentions"""
mentions = set([status.user] + list(status.mention_users.all())) mentions = set([status.user] + list(status.mention_users.all()))
return ( return (
@ -21,7 +22,7 @@ def get_mentions(status, user):
@register.filter(name="replies") @register.filter(name="replies")
def get_replies(status): def get_replies(status: models.Status) -> Any:
"""get all direct replies to a status""" """get all direct replies to a status"""
# TODO: this limit could cause problems # TODO: this limit could cause problems
return models.Status.objects.filter( return models.Status.objects.filter(
@ -31,7 +32,7 @@ def get_replies(status):
@register.filter(name="parent") @register.filter(name="parent")
def get_parent(status): def get_parent(status: models.Status) -> Any:
"""get the reply parent for a status""" """get the reply parent for a status"""
return ( return (
models.Status.objects.filter(id=status.reply_parent_id) models.Status.objects.filter(id=status.reply_parent_id)
@ -41,7 +42,7 @@ def get_parent(status):
@register.filter(name="boosted_status") @register.filter(name="boosted_status")
def get_boosted(boost): def get_boosted(boost: models.Boost) -> Any:
"""load a boosted status. have to do this or it won't get foreign keys""" """load a boosted status. have to do this or it won't get foreign keys"""
return ( return (
models.Status.objects.select_subclasses() models.Status.objects.select_subclasses()
@ -52,7 +53,7 @@ def get_boosted(boost):
@register.filter(name="published_date") @register.filter(name="published_date")
def get_published_date(date): def get_published_date(date: str) -> Any:
"""less verbose combo of humanize filters""" """less verbose combo of humanize filters"""
if not date: if not date:
return "" return ""
@ -66,7 +67,7 @@ def get_published_date(date):
@register.simple_tag() @register.simple_tag()
def get_header_template(status): def get_header_template(status: models.Status) -> Any:
"""get the path for the status template""" """get the path for the status template"""
if isinstance(status, models.Boost): if isinstance(status, models.Boost):
status = status.boosted_status status = status.boosted_status
@ -82,6 +83,6 @@ def get_header_template(status):
@register.simple_tag(takes_context=False) @register.simple_tag(takes_context=False)
def load_book(status): def load_book(status: models.Status) -> Optional[models.Book]:
"""how many users that you follow, follow them""" """how many users that you follow, follow them"""
return status.book if hasattr(status, "book") else status.mention_books.first() return status.book if hasattr(status, "book") else status.mention_books.first()

View file

@ -1,12 +1,14 @@
""" template filters """ """ template filters """
from typing import Any, Optional
from django import template from django import template
from bookwyrm import models
register = template.Library() register = template.Library()
@register.simple_tag(takes_context=True) @register.simple_tag(takes_context=True)
def mutuals_count(context, user): def mutuals_count(context: dict[str, Any], user: models.User) -> Optional[int]:
"""how many users that you follow, follow them""" """how many users that you follow, follow them"""
viewer = context["request"].user viewer = context["request"].user
if not viewer.is_authenticated: if not viewer.is_authenticated:

View file

@ -1,46 +1,50 @@
""" template filters for really common utilities """ """ template filters for really common utilities """
from typing import Any, Optional
import os import os
import re import re
from uuid import uuid4 from uuid import uuid4
from urllib.parse import urlparse from urllib.parse import urlparse
from django import template from django import template
from django.contrib.auth.models import Group
from django.utils.safestring import mark_safe from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from django.templatetags.static import static from django.templatetags.static import static
from django_stubs_ext import StrPromise
from bookwyrm.models import User from bookwyrm.models import Author, Edition, User
from bookwyrm.settings import INSTANCE_ACTOR_USERNAME from bookwyrm.settings import INSTANCE_ACTOR_USERNAME
register = template.Library() register = template.Library()
@register.filter(name="uuid") @register.filter(name="uuid")
def get_uuid(identifier): def get_uuid(identifier: str) -> str:
"""for avoiding clashing ids when there are many forms""" """for avoiding clashing ids when there are many forms"""
return f"{identifier}{uuid4()}" return f"{identifier}{uuid4()}"
@register.simple_tag(takes_context=False) @register.simple_tag(takes_context=False)
def join(*args): def join(*args: tuple[Any]) -> str:
"""concatenate an arbitrary set of values""" """concatenate an arbitrary set of values"""
return "_".join(str(a) for a in args) return "_".join(str(a) for a in args)
@register.filter(name="username") @register.filter(name="username")
def get_user_identifier(user): def get_user_identifier(user: User) -> str:
"""use localname for local users, username for remote""" """use localname for local users, username for remote"""
return user.localname if user.localname else user.username return user.localname if user.localname else user.username or ""
@register.filter(name="user_from_remote_id") @register.filter(name="user_from_remote_id")
def get_user_identifier_from_remote_id(remote_id): def get_user_identifier_from_remote_id(remote_id: str) -> Optional[User]:
"""get the local user id from their remote id""" """get the local user id from their remote id"""
user = User.objects.get(remote_id=remote_id) user = User.objects.get(remote_id=remote_id)
return user if user else None return user if user else None
@register.filter(name="book_title") @register.filter(name="book_title")
def get_title(book, too_short=5): def get_title(book: Edition, too_short: int = 5) -> Any:
"""display the subtitle if the title is short""" """display the subtitle if the title is short"""
if not book: if not book:
return "" return ""
@ -54,7 +58,7 @@ def get_title(book, too_short=5):
@register.simple_tag(takes_context=False) @register.simple_tag(takes_context=False)
def comparison_bool(str1, str2, reverse=False): def comparison_bool(str1: str, str2: str, reverse: bool = False) -> bool:
"""idk why I need to write a tag for this, it returns a bool""" """idk why I need to write a tag for this, it returns a bool"""
if reverse: if reverse:
return str1 != str2 return str1 != str2
@ -62,7 +66,7 @@ def comparison_bool(str1, str2, reverse=False):
@register.filter(is_safe=True) @register.filter(is_safe=True)
def truncatepath(value, arg): def truncatepath(value: Any, arg: Any) -> Any:
"""Truncate a path by removing all directories except the first and truncating""" """Truncate a path by removing all directories except the first and truncating"""
path = os.path.normpath(value.name) path = os.path.normpath(value.name)
path_list = path.split(os.sep) path_list = path.split(os.sep)
@ -74,7 +78,9 @@ def truncatepath(value, arg):
@register.simple_tag(takes_context=False) @register.simple_tag(takes_context=False)
def get_book_cover_thumbnail(book, size="medium", ext="jpg"): def get_book_cover_thumbnail(
book: Edition, size: str = "medium", ext: str = "jpg"
) -> Any:
"""Returns a book thumbnail at the specified size and extension, """Returns a book thumbnail at the specified size and extension,
with fallback if needed""" with fallback if needed"""
if size == "": if size == "":
@ -87,7 +93,7 @@ def get_book_cover_thumbnail(book, size="medium", ext="jpg"):
@register.filter(name="get_isni_bio") @register.filter(name="get_isni_bio")
def get_isni_bio(existing, author): def get_isni_bio(existing: int, author: Author) -> str:
"""Returns the isni bio string if an existing author has an isni listed""" """Returns the isni bio string if an existing author has an isni listed"""
auth_isni = re.sub(r"\D", "", str(author.isni)) auth_isni = re.sub(r"\D", "", str(author.isni))
if len(existing) == 0: if len(existing) == 0:
@ -101,7 +107,7 @@ def get_isni_bio(existing, author):
# pylint: disable=unused-argument # pylint: disable=unused-argument
@register.filter(name="get_isni", needs_autoescape=True) @register.filter(name="get_isni", needs_autoescape=True)
def get_isni(existing, author, autoescape=True): def get_isni(existing: str, author: Author, autoescape: bool = True) -> str:
"""Returns the isni ID if an existing author has an ISNI listing""" """Returns the isni ID if an existing author has an ISNI listing"""
auth_isni = re.sub(r"\D", "", str(author.isni)) auth_isni = re.sub(r"\D", "", str(author.isni))
if len(existing) == 0: if len(existing) == 0:
@ -116,7 +122,7 @@ def get_isni(existing, author, autoescape=True):
@register.simple_tag(takes_context=False) @register.simple_tag(takes_context=False)
def id_to_username(user_id): def id_to_username(user_id: str) -> str | StrPromise:
"""given an arbitrary remote id, return the username""" """given an arbitrary remote id, return the username"""
if user_id: if user_id:
url = urlparse(user_id) url = urlparse(user_id)
@ -130,7 +136,7 @@ def id_to_username(user_id):
@register.filter(name="get_file_size") @register.filter(name="get_file_size")
def get_file_size(nbytes): def get_file_size(nbytes: int) -> str:
"""display the size of a file in human readable terms""" """display the size of a file in human readable terms"""
try: try:
@ -148,13 +154,13 @@ def get_file_size(nbytes):
@register.filter(name="get_user_permission") @register.filter(name="get_user_permission")
def get_user_permission(user): def get_user_permission(user: User) -> Group | str:
"""given a user, return their permission level""" """given a user, return their permission level"""
return user.groups.first() or "User" return user.groups.first() or "User"
@register.filter(name="is_instance_admin") @register.filter(name="is_instance_admin")
def is_instance_admin(localname): def is_instance_admin(localname: str) -> bool:
"""Returns a boolean indicating whether the user is the instance admin account""" """Returns a boolean indicating whether the user is the instance admin account"""
return localname == INSTANCE_ACTOR_USERNAME return localname == INSTANCE_ACTOR_USERNAME

View file

@ -10,23 +10,19 @@ django_settings_module = "bookwyrm.settings"
ignore_errors = True ignore_errors = True
implicit_reexport = True implicit_reexport = True
[mypy-bookwyrm.connectors.*]
ignore_errors = False
[mypy-bookwyrm.models.author] [mypy-bookwyrm.models.author]
ignore_errors = False ignore_errors = False
allow_untyped_calls = True allow_untyped_calls = True
disable_error_code = import-untyped, assignment disable_error_code = import-untyped, assignment
[mypy-bookwyrm.connectors.*]
[mypy-bookwyrm.utils.*] [mypy-bookwyrm.utils.*]
ignore_errors = False
[mypy-bookwyrm.importers.*] [mypy-bookwyrm.importers.*]
ignore_errors = False
[mypy-bookwyrm.isbn.*] [mypy-bookwyrm.isbn.*]
ignore_errors = False
[mypy-celerywyrm.*] [mypy-celerywyrm.*]
ignore_errors = False ignore_errors = False
[mypy-bookwyrm.templatetags.*]
ignore_errors = False
allow_untyped_calls = True
disable_error_code = attr-defined, arg-type, misc