diff --git a/tildes/consumers/post_processing_script_runner.py b/tildes/consumers/post_processing_script_runner.py
index 963ea5d..81195df 100644
--- a/tildes/consumers/post_processing_script_runner.py
+++ b/tildes/consumers/post_processing_script_runner.py
@@ -3,7 +3,7 @@
"""Consumer that runs processing scripts on posts."""
-from typing import Type, Union
+from typing import Union
from sqlalchemy import desc
from sqlalchemy.sql.expression import or_
@@ -23,7 +23,7 @@ class PostProcessingScriptRunner(EventStreamConsumer):
def process_message(self, message: Message) -> None:
"""Process a message from the stream."""
- wrapper_class: Union[Type[TopicScriptingWrapper], Type[CommentScriptingWrapper]]
+ wrapper_class: Union[type[TopicScriptingWrapper], type[CommentScriptingWrapper]]
if "topic_id" in message.fields:
post = (
diff --git a/tildes/consumers/site_icon_downloader.py b/tildes/consumers/site_icon_downloader.py
index 83af260..2b0b795 100644
--- a/tildes/consumers/site_icon_downloader.py
+++ b/tildes/consumers/site_icon_downloader.py
@@ -3,9 +3,10 @@
"""Consumer that downloads site icons using Embedly scraper data."""
+from collections.abc import Sequence
from io import BytesIO
from os import path
-from typing import Optional, Sequence
+from typing import Optional
import publicsuffix
import requests
diff --git a/tildes/consumers/topic_embedly_extractor.py b/tildes/consumers/topic_embedly_extractor.py
index 93b6fa6..087a371 100644
--- a/tildes/consumers/topic_embedly_extractor.py
+++ b/tildes/consumers/topic_embedly_extractor.py
@@ -4,8 +4,8 @@
"""Consumer that fetches data from Embedly's Extract API for link topics."""
import os
+from collections.abc import Sequence
from datetime import timedelta
-from typing import Sequence
from pyramid.paster import get_appsettings
from requests.exceptions import HTTPError, Timeout
diff --git a/tildes/consumers/topic_metadata_generator.py b/tildes/consumers/topic_metadata_generator.py
index ff5f6a7..7ea4842 100644
--- a/tildes/consumers/topic_metadata_generator.py
+++ b/tildes/consumers/topic_metadata_generator.py
@@ -3,7 +3,8 @@
"""Consumer that generates content_metadata for topics."""
-from typing import Any, Dict, Sequence
+from collections.abc import Sequence
+from typing import Any
from ipaddress import ip_address
import publicsuffix
@@ -61,7 +62,7 @@ class TopicMetadataGenerator(EventStreamConsumer):
)
@staticmethod
- def _generate_text_metadata(topic: Topic) -> Dict[str, Any]:
+ def _generate_text_metadata(topic: Topic) -> dict[str, Any]:
"""Generate metadata for a text topic (word count and excerpt)."""
if not topic.rendered_html:
return {}
@@ -81,7 +82,7 @@ class TopicMetadataGenerator(EventStreamConsumer):
except ValueError:
return False
- def _generate_link_metadata(self, topic: Topic) -> Dict[str, Any]:
+ def _generate_link_metadata(self, topic: Topic) -> dict[str, Any]:
"""Generate metadata for a link topic (domain)."""
if not topic.link:
return {}
diff --git a/tildes/consumers/topic_youtube_scraper.py b/tildes/consumers/topic_youtube_scraper.py
index 1c576e9..c00731e 100644
--- a/tildes/consumers/topic_youtube_scraper.py
+++ b/tildes/consumers/topic_youtube_scraper.py
@@ -4,8 +4,8 @@
"""Consumer that fetches data from YouTube's data API for relevant link topics."""
import os
+from collections.abc import Sequence
from datetime import timedelta
-from typing import Sequence
from pyramid.paster import get_appsettings
from requests.exceptions import HTTPError, Timeout
diff --git a/tildes/tildes/__init__.py b/tildes/tildes/__init__.py
index c4d62cc..829232e 100644
--- a/tildes/tildes/__init__.py
+++ b/tildes/tildes/__init__.py
@@ -3,8 +3,6 @@
"""Configure and initialize the Pyramid app."""
-from typing import Dict
-
import sentry_sdk
from marshmallow.exceptions import ValidationError
from paste.deploy.config import PrefixMiddleware
@@ -13,7 +11,7 @@ from sentry_sdk.integrations.pyramid import PyramidIntegration
from webassets import Bundle
-def main(global_config: Dict[str, str], **settings: str) -> PrefixMiddleware:
+def main(global_config: dict[str, str], **settings: str) -> PrefixMiddleware:
"""Configure and return a Pyramid WSGI application."""
config = Configurator(settings=settings)
diff --git a/tildes/tildes/auth.py b/tildes/tildes/auth.py
index b94929c..6b72f63 100644
--- a/tildes/tildes/auth.py
+++ b/tildes/tildes/auth.py
@@ -3,7 +3,8 @@
"""Configuration and functionality related to authentication/authorization."""
-from typing import Any, Optional, Sequence
+from collections.abc import Sequence
+from typing import Any, Optional
from pyramid.authentication import SessionAuthenticationPolicy
from pyramid.authorization import ACLAuthorizationPolicy
diff --git a/tildes/tildes/database.py b/tildes/tildes/database.py
index c1cf4b4..39d2732 100644
--- a/tildes/tildes/database.py
+++ b/tildes/tildes/database.py
@@ -3,7 +3,7 @@
"""Contains the database-related config updates and request methods."""
-from typing import Callable, Type
+from collections.abc import Callable
from pyramid.config import Configurator
from pyramid.request import Request
@@ -31,7 +31,7 @@ def obtain_lock(request: Request, lock_space: str, lock_value: int) -> None:
obtain_transaction_lock(request.db_session, lock_space, lock_value)
-def query_factory(request: Request, model_cls: Type[DatabaseModel]) -> ModelQuery:
+def query_factory(request: Request, model_cls: type[DatabaseModel]) -> ModelQuery:
"""Return a ModelQuery or subclass depending on model_cls specified."""
if model_cls == Comment:
return CommentQuery(request)
diff --git a/tildes/tildes/enums.py b/tildes/tildes/enums.py
index a17945a..cb5792a 100644
--- a/tildes/tildes/enums.py
+++ b/tildes/tildes/enums.py
@@ -5,7 +5,7 @@
import enum
from datetime import timedelta
-from typing import Any, List, Optional
+from typing import Any, Optional
from tildes.lib.datetime import utc_from_timestamp
@@ -109,7 +109,7 @@ class ContentMetadataFields(enum.Enum):
def detail_fields_for_content_type(
cls,
content_type: "TopicContentType",
- ) -> List["ContentMetadataFields"]:
+ ) -> list["ContentMetadataFields"]:
"""Return a list of fields to display for detail about a particular type."""
if content_type is TopicContentType.ARTICLE:
return [cls.WORD_COUNT, cls.PUBLISHED]
diff --git a/tildes/tildes/lib/auth.py b/tildes/tildes/lib/auth.py
index c59e431..16c1875 100644
--- a/tildes/tildes/lib/auth.py
+++ b/tildes/tildes/lib/auth.py
@@ -3,7 +3,7 @@
"""Functions to help with authorization, such as generating ACLs."""
-from typing import List, Optional
+from typing import Optional
from pyramid.security import Allow, Deny
@@ -14,7 +14,7 @@ def aces_for_permission(
required_permission: str,
group_id: Optional[int] = None,
granted_permission: Optional[str] = None,
-) -> List[AceType]:
+) -> list[AceType]:
"""Return the ACEs for manually-granted (or denied) entries in UserPermissions."""
aces = []
diff --git a/tildes/tildes/lib/database.py b/tildes/tildes/lib/database.py
index b0d7eab..e3d17bc 100644
--- a/tildes/tildes/lib/database.py
+++ b/tildes/tildes/lib/database.py
@@ -4,7 +4,8 @@
"""Constants/classes/functions related to the database."""
import enum
-from typing import Any, Callable, List, Optional
+from collections.abc import Callable
+from typing import Any, Optional
from dateutil.rrule import rrule, rrulestr
from pyramid.paster import bootstrap
@@ -106,7 +107,7 @@ class ArrayOfLtree(ARRAY):
"""Return a conversion function for processing result row values."""
super_rp = super().result_processor(dialect, coltype)
- def handle_raw_string(value: str) -> List[str]:
+ def handle_raw_string(value: str) -> list[str]:
if not (value.startswith("{") and value.endswith("}")):
raise ValueError("%s is not an array value" % value)
@@ -119,7 +120,7 @@ class ArrayOfLtree(ARRAY):
return value.split(",")
- def process(value: Optional[str]) -> Optional[List[str]]:
+ def process(value: Optional[str]) -> Optional[list[str]]:
if value is None:
return None
@@ -183,10 +184,10 @@ class TagList(TypeDecorator):
impl = ArrayOfLtree
- def process_bind_param(self, value: str, dialect: Dialect) -> List[Ltree]:
+ def process_bind_param(self, value: str, dialect: Dialect) -> list[Ltree]:
"""Convert the value to ltree[] for storing."""
return [Ltree(tag.replace(" ", "_")) for tag in value]
- def process_result_value(self, value: List[Ltree], dialect: Dialect) -> List[str]:
+ def process_result_value(self, value: list[Ltree], dialect: Dialect) -> list[str]:
"""Convert the stored value to a list of strings."""
return [str(tag).replace("_", " ") for tag in value]
diff --git a/tildes/tildes/lib/event_stream.py b/tildes/tildes/lib/event_stream.py
index 6dfa41b..e752dbc 100644
--- a/tildes/tildes/lib/event_stream.py
+++ b/tildes/tildes/lib/event_stream.py
@@ -5,8 +5,9 @@
import os
from abc import abstractmethod
+from collections.abc import Sequence
from configparser import ConfigParser
-from typing import Any, Dict, List, Optional, Sequence
+from typing import Any, Optional
from prometheus_client import CollectorRegistry, Counter, start_http_server
from redis import Redis, ResponseError
@@ -22,7 +23,7 @@ class Message:
"""Represents a single message taken from a stream."""
def __init__(
- self, redis: Redis, stream: str, message_id: str, fields: Dict[str, str]
+ self, redis: Redis, stream: str, message_id: str, fields: dict[str, str]
):
"""Initialize a new message from a Redis stream."""
self.redis = redis
@@ -181,7 +182,7 @@ class EventStreamConsumer:
message_ids=[entry["message_id"]],
)
- def _xreadgroup_response_to_messages(self, response: Any) -> List[Message]:
+ def _xreadgroup_response_to_messages(self, response: Any) -> list[Message]:
"""Convert a response from XREADGROUP to a list of Messages."""
messages = []
@@ -204,7 +205,7 @@ class EventStreamConsumer:
return messages
- def _get_messages(self, pending: bool = False) -> List[Message]:
+ def _get_messages(self, pending: bool = False) -> list[Message]:
"""Get any messages from the streams for this consumer.
This method will return at most one message from each of the source streams per
diff --git a/tildes/tildes/lib/lua.py b/tildes/tildes/lib/lua.py
index 27d420e..fc3cf52 100644
--- a/tildes/tildes/lib/lua.py
+++ b/tildes/tildes/lib/lua.py
@@ -3,8 +3,9 @@
"""Functions and classes related to Lua scripting."""
+from collections.abc import Callable
from pathlib import Path
-from typing import Any, Callable, Optional
+from typing import Any, Optional
from lupa import LuaError, LuaRuntime
diff --git a/tildes/tildes/lib/markdown.py b/tildes/tildes/lib/markdown.py
index be3b232..f2dd4c8 100644
--- a/tildes/tildes/lib/markdown.py
+++ b/tildes/tildes/lib/markdown.py
@@ -4,19 +4,9 @@
"""Functions/constants related to markdown handling."""
import re
+from collections.abc import Callable, Iterator
from functools import partial
-from typing import (
- Any,
- Callable,
- Dict,
- Iterator,
- List,
- Match,
- Optional,
- Pattern,
- Tuple,
- Union,
-)
+from typing import Any, Optional, Union
import bleach
from bs4 import BeautifulSoup
@@ -105,7 +95,7 @@ ALLOWED_HTML_TAGS = (
)
ALLOWED_LINK_PROTOCOLS = ("gemini", "http", "https", "mailto")
-ALLOWED_HTML_ATTRIBUTES_DEFAULT: Dict[str, Union[List[str], Callable]] = {
+ALLOWED_HTML_ATTRIBUTES_DEFAULT: dict[str, Union[list[str], Callable]] = {
"a": ["href", "title"],
"details": ["open"],
"ol": ["start"],
@@ -234,7 +224,7 @@ class CodeHtmlFormatter(HtmlFormatter):
...
instead (assumes a
is already present).
"""
- def wrap(self, source: Any, outfile: Any) -> Iterator[Tuple[int, str]]:
+ def wrap(self, source: Any, outfile: Any) -> Iterator[tuple[int, str]]:
"""Wrap the highlighted tokens with the tag."""
# pylint: disable=unused-argument
yield (0, '')
@@ -311,7 +301,7 @@ class LinkifyFilter(Filter):
SUBREDDIT_REFERENCE_REGEX = re.compile(r"(? List[dict]:
+ tokens: list[dict], filter_regex: re.Pattern, linkify_function: Callable
+ ) -> list[dict]:
"""Check tokens for text that matches a regex and linkify it.
The `filter_regex` argument should be a compiled pattern that will be applied to
@@ -434,7 +424,7 @@ class LinkifyFilter(Filter):
return new_tokens
@staticmethod
- def _tokenize_group_match(match: Match) -> List[dict]:
+ def _tokenize_group_match(match: re.Match) -> list[dict]:
"""Convert a potential group reference into HTML tokens."""
# convert the potential group path to lowercase to allow people to use incorrect
# casing but still have it link properly
@@ -465,7 +455,7 @@ class LinkifyFilter(Filter):
return [{"type": "Characters", "data": match[0]}]
@staticmethod
- def _tokenize_username_match(match: Match) -> List[dict]:
+ def _tokenize_username_match(match: re.Match) -> list[dict]:
"""Convert a potential username reference into HTML tokens."""
# if it's a valid username, convert to
if is_valid_username(match[1]):
@@ -486,7 +476,7 @@ class LinkifyFilter(Filter):
return [{"type": "Characters", "data": match[0]}]
@staticmethod
- def _tokenize_subreddit_match(match: Match) -> List[dict]:
+ def _tokenize_subreddit_match(match: re.Match) -> list[dict]:
"""Convert a subreddit reference into HTML tokens."""
return [
{
diff --git a/tildes/tildes/lib/ratelimit.py b/tildes/tildes/lib/ratelimit.py
index 9bd7002..fb38765 100644
--- a/tildes/tildes/lib/ratelimit.py
+++ b/tildes/tildes/lib/ratelimit.py
@@ -4,9 +4,10 @@
"""Classes and constants related to rate-limited actions."""
from __future__ import annotations
+from collections.abc import Sequence
from datetime import timedelta
from ipaddress import ip_address
-from typing import Any, List, Optional, Sequence
+from typing import Any, Optional
from pyramid.response import Response
from redis import Redis
@@ -69,7 +70,7 @@ class RateLimitResult:
)
@classmethod
- def from_redis_cell_result(cls, result: List[int]) -> RateLimitResult:
+ def from_redis_cell_result(cls, result: list[int]) -> RateLimitResult:
"""Convert the response from CL.THROTTLE command to a RateLimitResult.
CL.THROTTLE responds with an array of 5 integers:
@@ -228,7 +229,7 @@ class RateLimitedAction:
return ":".join(parts)
- def _call_redis_command(self, key: str) -> List[int]:
+ def _call_redis_command(self, key: str) -> list[int]:
"""Call the redis-cell CL.THROTTLE command for this action."""
return self.redis.execute_command(
"CL.THROTTLE",
diff --git a/tildes/tildes/lib/site_info.py b/tildes/tildes/lib/site_info.py
index c4acc48..c48faac 100644
--- a/tildes/tildes/lib/site_info.py
+++ b/tildes/tildes/lib/site_info.py
@@ -3,7 +3,7 @@
"""Library code related to displaying info about individual websites."""
-from typing import List, Optional
+from typing import Optional
from tildes.enums import ContentMetadataFields, TopicContentType
@@ -22,7 +22,7 @@ class SiteInfo:
self.show_author = show_author
self.content_type = content_type
- def content_source(self, authors: Optional[List[str]] = None) -> str:
+ def content_source(self, authors: Optional[list[str]] = None) -> str:
"""Return a string representing the "source" of content on this site.
If the site isn't one that needs to show its author, this is just its name.
diff --git a/tildes/tildes/lib/string.py b/tildes/tildes/lib/string.py
index 74e3181..51c5e62 100644
--- a/tildes/tildes/lib/string.py
+++ b/tildes/tildes/lib/string.py
@@ -5,7 +5,8 @@
import re
import unicodedata
-from typing import Iterator, List, Optional
+from collections.abc import Iterator
+from typing import Optional
from urllib.parse import quote
from xml.etree.ElementTree import Element
@@ -225,10 +226,10 @@ def separate_string(original: str, separator: str, segment_size: int) -> str:
return separated
-def extract_text_from_html(html: str, skip_tags: Optional[List[str]] = None) -> str:
+def extract_text_from_html(html: str, skip_tags: Optional[list[str]] = None) -> str:
"""Extract plain text content from the elements inside an HTML string."""
- def extract_text(element: Element, skip_tags: List[str]) -> Iterator[str]:
+ def extract_text(element: Element, skip_tags: list[str]) -> Iterator[str]:
"""Extract text recursively from elements, optionally skipping some tags.
This function is Python's xml.etree.ElementTree.Element.itertext() but with the
diff --git a/tildes/tildes/metrics.py b/tildes/tildes/metrics.py
index 5b8dcd2..491f6ba 100644
--- a/tildes/tildes/metrics.py
+++ b/tildes/tildes/metrics.py
@@ -7,7 +7,7 @@
# checks to avoid errors
# pylint: disable=no-value-for-parameter,redundant-keyword-arg
-from typing import Callable
+from collections.abc import Callable
from prometheus_client import Counter, Histogram, Summary
diff --git a/tildes/tildes/models/comment/comment.py b/tildes/tildes/models/comment/comment.py
index 65cd0f2..a4bc90b 100644
--- a/tildes/tildes/models/comment/comment.py
+++ b/tildes/tildes/models/comment/comment.py
@@ -4,8 +4,9 @@
"""Contains the Comment class."""
from collections import Counter
+from collections.abc import Sequence
from datetime import datetime, timedelta
-from typing import Any, Optional, Sequence, TYPE_CHECKING, Union
+from typing import Any, Optional, TYPE_CHECKING, Union
from pyramid.security import (
Allow,
diff --git a/tildes/tildes/models/comment/comment_notification.py b/tildes/tildes/models/comment/comment_notification.py
index 18167bd..647837e 100644
--- a/tildes/tildes/models/comment/comment_notification.py
+++ b/tildes/tildes/models/comment/comment_notification.py
@@ -5,7 +5,6 @@
import re
from datetime import datetime
-from typing import List, Tuple
from pyramid.security import Allow, DENY_ALL
from sqlalchemy import BigInteger, Boolean, Column, ForeignKey, TIMESTAMP
@@ -120,7 +119,7 @@ class CommentNotification(DatabaseModel):
@classmethod
def get_mentions_for_comment(
cls, db_session: Session, comment: Comment
- ) -> List["CommentNotification"]:
+ ) -> list["CommentNotification"]:
"""Get a list of notifications for user mentions in the comment."""
notifications = []
@@ -160,8 +159,8 @@ class CommentNotification(DatabaseModel):
def prevent_duplicate_notifications(
db_session: Session,
comment: Comment,
- new_notifications: List["CommentNotification"],
- ) -> Tuple[List["CommentNotification"], List["CommentNotification"]]:
+ new_notifications: list["CommentNotification"],
+ ) -> tuple[list["CommentNotification"], list["CommentNotification"]]:
"""Filter new notifications for edited comments.
Protect against sending a notification for the same comment to the same user
diff --git a/tildes/tildes/models/comment/comment_tree.py b/tildes/tildes/models/comment/comment_tree.py
index fbf2895..0fc4a25 100644
--- a/tildes/tildes/models/comment/comment_tree.py
+++ b/tildes/tildes/models/comment/comment_tree.py
@@ -4,8 +4,9 @@
"""Contains the CommentTree and CommentInTree classes."""
from collections import Counter
+from collections.abc import Iterator, Sequence
from datetime import datetime
-from typing import Iterator, List, Optional, Sequence, Tuple
+from typing import Optional
from prometheus_client import Histogram
from wrapt import ObjectProxy
@@ -27,7 +28,7 @@ class CommentTree:
viewer: Optional[User] = None,
):
"""Create a sorted CommentTree from a flat list of Comments."""
- self.tree: List[CommentInTree] = []
+ self.tree: list[CommentInTree] = []
self.sort = sort
self.viewer = viewer
@@ -92,7 +93,7 @@ class CommentTree:
self.tree.append(comment)
@staticmethod
- def _sort_tree(tree: List[Comment], sort: CommentTreeSortOption) -> List[Comment]:
+ def _sort_tree(tree: list[Comment], sort: CommentTreeSortOption) -> list[Comment]:
"""Sort the tree by the desired ordering (recursively).
Because Python's sorted() function is stable, the ordering of any comments that
@@ -118,7 +119,7 @@ class CommentTree:
return tree
@staticmethod
- def _prune_empty_branches(tree: Sequence[Comment]) -> List[Comment]:
+ def _prune_empty_branches(tree: Sequence[Comment]) -> list[Comment]:
"""Remove branches from the tree with no visible comments."""
pruned_tree = []
@@ -269,7 +270,7 @@ class CommentInTree(ObjectProxy):
super().__init__(comment)
self.collapsed_state: Optional[str] = None
- self.replies: List[CommentInTree] = []
+ self.replies: list[CommentInTree] = []
self.has_visible_descendant = False
self.num_children = 0
self.depth = 0
@@ -308,7 +309,7 @@ class CommentInTree(ObjectProxy):
reply.recursively_collapse()
@property
- def relevance_sorting_value(self) -> Tuple[int, ...]:
+ def relevance_sorting_value(self) -> tuple[int, ...]:
"""Value to use for the comment with the "relevance" comment sorting method.
Returns a tuple, which allows sorting the comments into "tiers" and then still
diff --git a/tildes/tildes/models/database_model.py b/tildes/tildes/models/database_model.py
index 3b6662b..c94175c 100644
--- a/tildes/tildes/models/database_model.py
+++ b/tildes/tildes/models/database_model.py
@@ -4,7 +4,7 @@
"""Contains the base DatabaseModel class."""
from datetime import timedelta
-from typing import Any, Optional, Type, TypeVar
+from typing import Any, Optional, TypeVar
from marshmallow import Schema
from sqlalchemy import event
@@ -28,7 +28,7 @@ NAMING_CONVENTION = {
def attach_set_listener(
- class_: Type["DatabaseModelBase"], attribute: str, instance: "DatabaseModelBase"
+ class_: type["DatabaseModelBase"], attribute: str, instance: "DatabaseModelBase"
) -> None:
"""Attach the SQLAlchemy ORM "set" attribute listener."""
# pylint: disable=unused-argument
@@ -48,7 +48,7 @@ class DatabaseModelBase:
# declare the type of __table__ so mypy understands it when checking __eq__
__table__: Table
- schema_class: Optional[Type[Schema]] = None
+ schema_class: Optional[type[Schema]] = None
def __eq__(self, other: Any) -> bool:
"""Equality comparison method - check if primary key values match."""
diff --git a/tildes/tildes/models/group/group.py b/tildes/tildes/models/group/group.py
index ecd9d47..5523b90 100644
--- a/tildes/tildes/models/group/group.py
+++ b/tildes/tildes/models/group/group.py
@@ -4,7 +4,7 @@
"""Contains the Group class."""
from datetime import datetime
-from typing import List, Optional
+from typing import Optional
from pyramid.security import Allow, Authenticated, Deny, DENY_ALL, Everyone
from sqlalchemy import (
@@ -67,8 +67,8 @@ class Group(DatabaseModel):
is_user_treated_as_topic_source: bool = Column(
Boolean, nullable=False, server_default="false"
)
- common_topic_tags: List[str] = Column(TagList, nullable=False, server_default="{}")
- important_topic_tags: List[str] = Column(
+ common_topic_tags: list[str] = Column(TagList, nullable=False, server_default="{}")
+ important_topic_tags: list[str] = Column(
TagList, nullable=False, server_default="{}"
)
@@ -96,7 +96,7 @@ class Group(DatabaseModel):
self.sidebar_rendered_html = None
@property
- def autocomplete_topic_tags(self) -> List[str]:
+ def autocomplete_topic_tags(self) -> list[str]:
"""Return the topic tags that should be offered as autocomplete options."""
global_options = ["nsfw", "spoiler", "coronaviruses.covid19"]
diff --git a/tildes/tildes/models/group/group_wiki_page.py b/tildes/tildes/models/group/group_wiki_page.py
index 914a2a1..6e2404a 100644
--- a/tildes/tildes/models/group/group_wiki_page.py
+++ b/tildes/tildes/models/group/group_wiki_page.py
@@ -5,7 +5,7 @@
from datetime import datetime
from pathlib import Path, PurePath
-from typing import List, Optional
+from typing import Optional
from pygit2 import Repository, Signature
from pyramid.security import Allow, DENY_ALL, Everyone
@@ -104,7 +104,7 @@ class GroupWikiPage(DatabaseModel):
return self.file_path.stem
@property
- def folders(self) -> List[PurePath]:
+ def folders(self) -> list[PurePath]:
"""Return a list of the folders the page is inside (if any)."""
path = PurePath(self.path)
diff --git a/tildes/tildes/models/log/log.py b/tildes/tildes/models/log/log.py
index 3874414..152fad5 100644
--- a/tildes/tildes/models/log/log.py
+++ b/tildes/tildes/models/log/log.py
@@ -3,7 +3,7 @@
"""Contains the Log class."""
-from typing import Any, Dict, Optional
+from typing import Any, Optional
from pyramid.request import Request
from sqlalchemy import BigInteger, Column, event, ForeignKey, Table, TIMESTAMP
@@ -75,7 +75,7 @@ class Log(DatabaseModel, BaseLog):
self,
event_type: LogEventType,
request: Request,
- info: Optional[Dict[str, Any]] = None,
+ info: Optional[dict[str, Any]] = None,
):
"""Create a new log entry.
@@ -106,7 +106,7 @@ class LogComment(DatabaseModel, BaseLog):
event_type: LogEventType,
request: Request,
comment: Comment,
- info: Optional[Dict[str, Any]] = None,
+ info: Optional[dict[str, Any]] = None,
):
"""Create a new log entry related to a specific comment."""
# pylint: disable=non-parent-init-called
@@ -135,7 +135,7 @@ class LogTopic(DatabaseModel, BaseLog):
event_type: LogEventType,
request: Request,
topic: Topic,
- info: Optional[Dict[str, Any]] = None,
+ info: Optional[dict[str, Any]] = None,
):
"""Create a new log entry related to a specific topic."""
# pylint: disable=non-parent-init-called
diff --git a/tildes/tildes/models/message/message.py b/tildes/tildes/models/message/message.py
index c200489..0fa0e38 100644
--- a/tildes/tildes/models/message/message.py
+++ b/tildes/tildes/models/message/message.py
@@ -12,8 +12,9 @@ This might feel a bit unusual since it splits "all messages" across two tables/c
but it simplifies a lot of things when organizing them into threads.
"""
+from collections.abc import Sequence
from datetime import datetime
-from typing import List, Optional, Sequence
+from typing import Optional
from pyramid.security import Allow, DENY_ALL
from sqlalchemy import (
@@ -91,7 +92,7 @@ class MessageConversation(DatabaseModel):
# is dangerous and *will* break if user_id values ever get larger than integers
# can hold. I'm comfortable doing something that will only be an issue if the site
# reaches 2.1 billion users though, I think this would be the least of the problems.
- unread_user_ids: List[int] = Column(
+ unread_user_ids: list[int] = Column(
ARRAY(Integer), nullable=False, server_default="{}"
)
diff --git a/tildes/tildes/models/model_query.py b/tildes/tildes/models/model_query.py
index 2203772..74cde0d 100644
--- a/tildes/tildes/models/model_query.py
+++ b/tildes/tildes/models/model_query.py
@@ -5,7 +5,8 @@
# pylint: disable=self-cls-assignment
from __future__ import annotations
-from typing import Any, Iterator, TypeVar
+from collections.abc import Iterator
+from typing import Any, TypeVar
from pyramid.request import Request
from sqlalchemy import event
diff --git a/tildes/tildes/models/pagination.py b/tildes/tildes/models/pagination.py
index 86312bd..9727502 100644
--- a/tildes/tildes/models/pagination.py
+++ b/tildes/tildes/models/pagination.py
@@ -4,8 +4,9 @@
"""Contains the PaginatedQuery and PaginatedResults classes."""
from __future__ import annotations
+from collections.abc import Iterator, Sequence
from itertools import chain
-from typing import Any, Iterator, List, Optional, Sequence, TypeVar
+from typing import Any, Optional, TypeVar
from pyramid.request import Request
from sqlalchemy import Column, func, inspect
@@ -39,12 +40,12 @@ class PaginatedQuery(ModelQuery):
if not self.is_reversed:
return super().__iter__()
- results: List[ModelType] = list(super().__iter__())
+ results: list[ModelType] = list(super().__iter__())
return iter(reversed(results))
@property
- def sorting_columns(self) -> List[Column]:
+ def sorting_columns(self) -> list[Column]:
"""Return the columns being used for sorting."""
if not self._sort_column:
raise AttributeError
@@ -56,7 +57,7 @@ class PaginatedQuery(ModelQuery):
return [self._sort_column]
@property
- def sorting_columns_desc(self) -> List[Column]:
+ def sorting_columns_desc(self) -> list[Column]:
"""Return descending versions of the sorting columns."""
return [col.desc() for col in self.sorting_columns]
diff --git a/tildes/tildes/models/topic/topic.py b/tildes/tildes/models/topic/topic.py
index 6e9435b..1c0fdd6 100644
--- a/tildes/tildes/models/topic/topic.py
+++ b/tildes/tildes/models/topic/topic.py
@@ -4,10 +4,11 @@
"""Contains the Topic class."""
from __future__ import annotations
+from collections.abc import Iterable
from datetime import datetime, timedelta
from itertools import chain
from pathlib import PurePosixPath
-from typing import Any, Dict, Iterable, List, Optional, TYPE_CHECKING
+from typing import Any, Optional, TYPE_CHECKING
from urllib.parse import urlparse
from pyramid.security import Allow, Authenticated, Deny, DENY_ALL, Everyone
@@ -122,7 +123,7 @@ class Topic(DatabaseModel):
_markdown: Optional[str] = deferred(Column("markdown", Text))
rendered_html: Optional[str] = Column(Text)
link: Optional[str] = Column(Text)
- content_metadata: Dict[str, Any] = Column(
+ content_metadata: dict[str, Any] = Column(
MutableDict.as_mutable(JSONB(none_as_null=True))
)
num_comments: int = Column(Integer, nullable=False, server_default="0")
@@ -130,7 +131,7 @@ class Topic(DatabaseModel):
_is_voting_closed: bool = Column(
"is_voting_closed", Boolean, nullable=False, server_default="false", index=True
)
- tags: List[str] = Column(TagList, nullable=False, server_default="{}")
+ tags: list[str] = Column(TagList, nullable=False, server_default="{}")
is_official: bool = Column(Boolean, nullable=False, server_default="false")
is_locked: bool = Column(Boolean, nullable=False, server_default="false")
search_tsv: Any = deferred(Column(TSVECTOR))
@@ -184,7 +185,7 @@ class Topic(DatabaseModel):
self.last_edited_time = utc_now()
@property
- def important_tags(self) -> List[str]:
+ def important_tags(self) -> list[str]:
"""Return only the topic's "important" tags."""
global_important_tags = ["nsfw", "spoiler"]
@@ -200,7 +201,7 @@ class Topic(DatabaseModel):
]
@property
- def unimportant_tags(self) -> List[str]:
+ def unimportant_tags(self) -> list[str]:
"""Return only the topic's tags that *aren't* considered "important"."""
important_tags = set(self.important_tags)
return [tag for tag in self.tags if tag not in important_tags]
@@ -552,7 +553,7 @@ class Topic(DatabaseModel):
return self.content_metadata.get(key)
@property
- def content_metadata_for_display(self) -> List[str]:
+ def content_metadata_for_display(self) -> list[str]:
"""Return a list of the content's metadata strings, suitable for display."""
if not self.content_type:
return []
@@ -582,7 +583,7 @@ class Topic(DatabaseModel):
return metadata_strings
@property
- def content_metadata_fields_for_display(self) -> Dict[str, str]:
+ def content_metadata_fields_for_display(self) -> dict[str, str]:
"""Return a dict of the metadata fields and values, suitable for display."""
if not self.content_metadata:
return {}
diff --git a/tildes/tildes/models/topic/topic_query.py b/tildes/tildes/models/topic/topic_query.py
index debf499..8eb63a8 100644
--- a/tildes/tildes/models/topic/topic_query.py
+++ b/tildes/tildes/models/topic/topic_query.py
@@ -4,7 +4,8 @@
"""Contains the TopicQuery class."""
from __future__ import annotations
-from typing import Any, Sequence
+from collections.abc import Sequence
+from typing import Any
from pyramid.request import Request
from sqlalchemy import func
diff --git a/tildes/tildes/models/topic/topic_schedule.py b/tildes/tildes/models/topic/topic_schedule.py
index 763a59a..4a2f8f5 100644
--- a/tildes/tildes/models/topic/topic_schedule.py
+++ b/tildes/tildes/models/topic/topic_schedule.py
@@ -4,7 +4,7 @@
"""Contains the TopicSchedule class."""
from datetime import datetime
-from typing import List, Optional
+from typing import Optional
from dateutil.rrule import rrule
from jinja2.sandbox import SandboxedEnvironment
@@ -58,7 +58,7 @@ class TopicSchedule(DatabaseModel):
nullable=False,
)
markdown: str = Column(Text, nullable=False)
- tags: List[str] = Column(TagList, nullable=False, server_default="{}")
+ tags: list[str] = Column(TagList, nullable=False, server_default="{}")
next_post_time: Optional[datetime] = Column(
TIMESTAMP(timezone=True), nullable=True, index=True
)
@@ -79,7 +79,7 @@ class TopicSchedule(DatabaseModel):
group: Group,
title: str,
markdown: str,
- tags: List[str],
+ tags: list[str],
next_post_time: datetime,
recurrence_rule: Optional[rrule] = None,
user: Optional[User] = None,
diff --git a/tildes/tildes/models/user/user.py b/tildes/tildes/models/user/user.py
index 86e6dac..933f4d7 100644
--- a/tildes/tildes/models/user/user.py
+++ b/tildes/tildes/models/user/user.py
@@ -4,7 +4,7 @@
"""Contains the User class."""
from datetime import datetime, timedelta
-from typing import List, NoReturn, Optional
+from typing import NoReturn, Optional
from pyotp import TOTP
from pyramid.security import (
@@ -84,7 +84,7 @@ class User(DatabaseModel):
)
two_factor_enabled: bool = Column(Boolean, nullable=False, server_default="false")
two_factor_secret: Optional[str] = deferred(Column(Text))
- two_factor_backup_codes: List[str] = deferred(Column(ARRAY(Text)))
+ two_factor_backup_codes: list[str] = deferred(Column(ARRAY(Text)))
created_time: datetime = Column(
TIMESTAMP(timezone=True),
nullable=False,
@@ -130,7 +130,7 @@ class User(DatabaseModel):
ban_expiry_time: Optional[datetime] = Column(TIMESTAMP(timezone=True))
home_default_order: Optional[TopicSortOption] = Column(ENUM(TopicSortOption))
home_default_period: Optional[str] = Column(Text)
- filtered_topic_tags: List[str] = Column(
+ filtered_topic_tags: list[str] = Column(
TagList, nullable=False, server_default="{}"
)
comment_label_weight: Optional[float] = Column(REAL)
@@ -322,7 +322,7 @@ class User(DatabaseModel):
return self.num_unread_messages + self.num_unread_notifications
@property
- def auth_principals(self) -> List[str]:
+ def auth_principals(self) -> list[str]:
"""Return the user's authorization principals (used for permissions)."""
principals = [permission.auth_principal for permission in self.permissions]
diff --git a/tildes/tildes/request_methods.py b/tildes/tildes/request_methods.py
index 8fcfd44..f6e25ae 100644
--- a/tildes/tildes/request_methods.py
+++ b/tildes/tildes/request_methods.py
@@ -3,7 +3,7 @@
"""Define and attach request methods to the Pyramid request object."""
-from typing import Any, Dict, Optional, Tuple
+from typing import Any, Optional
from pyramid.config import Configurator
from pyramid.httpexceptions import HTTPTooManyRequests
@@ -114,7 +114,7 @@ def apply_rate_limit(request: Request, action_name: str) -> None:
def current_listing_base_url(
- request: Request, query: Optional[Dict[str, Any]] = None
+ request: Request, query: Optional[dict[str, Any]] = None
) -> str:
"""Return the "base" url for the current listing route.
@@ -123,7 +123,7 @@ def current_listing_base_url(
The `query` argument allows adding query variables to the generated url.
"""
- base_vars_by_route: Dict[str, Tuple[str, ...]] = {
+ base_vars_by_route: dict[str, tuple[str, ...]] = {
"bookmarks": ("per_page", "type"),
"group": ("order", "period", "per_page", "tag", "unfiltered"),
"group_search": ("order", "period", "per_page", "q"),
@@ -149,7 +149,7 @@ def current_listing_base_url(
def current_listing_normal_url(
- request: Request, query: Optional[Dict[str, Any]] = None
+ request: Request, query: Optional[dict[str, Any]] = None
) -> str:
"""Return the "normal" url for the current listing route.
@@ -158,7 +158,7 @@ def current_listing_normal_url(
The `query` argument allows adding query variables to the generated url.
"""
- normal_vars_by_route: Dict[str, Tuple[str, ...]] = {
+ normal_vars_by_route: dict[str, tuple[str, ...]] = {
"bookmarks": ("order", "period", "per_page"),
"votes": ("order", "period", "per_page"),
"group": ("order", "period", "per_page"),
diff --git a/tildes/tildes/schemas/fields.py b/tildes/tildes/schemas/fields.py
index 436fbb9..ad8770b 100644
--- a/tildes/tildes/schemas/fields.py
+++ b/tildes/tildes/schemas/fields.py
@@ -5,7 +5,8 @@
import enum
import re
-from typing import Any, Mapping, Optional, Type
+from collections.abc import Mapping
+from typing import Any, Optional
import sqlalchemy_utils
from marshmallow.exceptions import ValidationError
@@ -24,7 +25,9 @@ DataType = Optional[Mapping[str, Any]]
class Enum(Field):
"""Field for a native Python Enum (or subclasses)."""
- def __init__(self, enum_class: Optional[Type] = None, *args: Any, **kwargs: Any):
+ def __init__(
+ self, enum_class: Optional[type[enum.Enum]] = None, *args: Any, **kwargs: Any
+ ):
"""Initialize the field with an optional enum class."""
# pylint: disable=keyword-arg-before-vararg
super().__init__(*args, **kwargs)
diff --git a/tildes/tildes/schemas/topic.py b/tildes/tildes/schemas/topic.py
index 289b0f7..41e2aae 100644
--- a/tildes/tildes/schemas/topic.py
+++ b/tildes/tildes/schemas/topic.py
@@ -4,7 +4,6 @@
"""Validation/dumping schema for topics."""
import re
-import typing
from typing import Any
from urllib.parse import urlparse
@@ -65,7 +64,7 @@ class TopicSchema(Schema):
new_data = data.copy()
- tags: typing.List[str] = []
+ tags: list[str] = []
for tag in new_data["tags"]:
tag = tag.lower()
@@ -99,7 +98,7 @@ class TopicSchema(Schema):
return new_data
@validates("tags")
- def validate_tags(self, value: typing.List[str]) -> None:
+ def validate_tags(self, value: list[str]) -> None:
"""Validate the tags field, raising an error if an issue exists.
Note that tags are validated by ensuring that each tag would be a valid group
diff --git a/tildes/tildes/scrapers/embedly_scraper.py b/tildes/tildes/scrapers/embedly_scraper.py
index de97de2..71e438b 100644
--- a/tildes/tildes/scrapers/embedly_scraper.py
+++ b/tildes/tildes/scrapers/embedly_scraper.py
@@ -3,7 +3,7 @@
"""Contains the EmbedlyScraper class."""
-from typing import Any, Dict
+from typing import Any
from urllib.parse import urlparse
import requests
@@ -35,7 +35,7 @@ class EmbedlyScraper:
def scrape_url(self, url: str) -> ScraperResult:
"""Scrape a url and return the result."""
- params: Dict[str, Any] = {"key": self.api_key, "format": "json", "url": url}
+ params: dict[str, Any] = {"key": self.api_key, "format": "json", "url": url}
response = requests.get(
"https://api.embedly.com/1/extract", params=params, timeout=5
@@ -45,7 +45,7 @@ class EmbedlyScraper:
return ScraperResult(url, ScraperType.EMBEDLY, response.json())
@staticmethod
- def get_metadata_from_result(result: ScraperResult) -> Dict[str, Any]:
+ def get_metadata_from_result(result: ScraperResult) -> dict[str, Any]:
"""Get the metadata that we're interested in out of a scrape result."""
if result.scraper_type != ScraperType.EMBEDLY:
raise ValueError("Can't process a result from a different scraper.")
diff --git a/tildes/tildes/scrapers/youtube_scraper.py b/tildes/tildes/scrapers/youtube_scraper.py
index c6ac65b..89cc804 100644
--- a/tildes/tildes/scrapers/youtube_scraper.py
+++ b/tildes/tildes/scrapers/youtube_scraper.py
@@ -5,7 +5,7 @@
import re
from datetime import timedelta
-from typing import Any, Dict
+from typing import Any
from urllib.parse import parse_qs, urlparse
import requests
@@ -59,7 +59,7 @@ class YoutubeScraper:
if not video_id:
raise ValueError("Invalid url, no video ID found.")
- params: Dict[str, Any] = {
+ params: dict[str, Any] = {
"key": self.api_key,
"id": video_id,
"part": "snippet,contentDetails,statistics",
@@ -80,7 +80,7 @@ class YoutubeScraper:
return ScraperResult(url, ScraperType.YOUTUBE, video_data)
@classmethod
- def get_metadata_from_result(cls, result: ScraperResult) -> Dict[str, Any]:
+ def get_metadata_from_result(cls, result: ScraperResult) -> dict[str, Any]:
"""Get the metadata that we're interested in out of a scrape result."""
if result.scraper_type != ScraperType.YOUTUBE:
raise ValueError("Can't process a result from a different scraper.")
diff --git a/tildes/tildes/tweens.py b/tildes/tildes/tweens.py
index b378534..9031c1f 100644
--- a/tildes/tildes/tweens.py
+++ b/tildes/tildes/tweens.py
@@ -3,8 +3,8 @@
"""Contains Pyramid "tweens", used to insert additional logic into request-handling."""
+from collections.abc import Callable
from time import time
-from typing import Callable
from prometheus_client import Histogram
from pyramid.config import Configurator
diff --git a/tildes/tildes/typing.py b/tildes/tildes/typing.py
index 3991e41..1818355 100644
--- a/tildes/tildes/typing.py
+++ b/tildes/tildes/typing.py
@@ -3,8 +3,8 @@
"""Custom type aliases to use in type annotations."""
-from typing import Any, List, Tuple
+from typing import Any
# types for an ACE (Access Control Entry), and the ACL (Access Control List) of them
-AceType = Tuple[str, Any, str]
-AclType = List[AceType]
+AceType = tuple[str, Any, str]
+AclType = list[AceType]
diff --git a/tildes/tildes/views/api/web/exceptions.py b/tildes/tildes/views/api/web/exceptions.py
index 40b4ac1..827ba61 100644
--- a/tildes/tildes/views/api/web/exceptions.py
+++ b/tildes/tildes/views/api/web/exceptions.py
@@ -3,7 +3,7 @@
"""Web API exception views."""
-from typing import Sequence
+from collections.abc import Sequence
from urllib.parse import quote, urlparse, urlunparse
from marshmallow.exceptions import ValidationError
diff --git a/tildes/tildes/views/bookmarks.py b/tildes/tildes/views/bookmarks.py
index 728cfe0..4c64fcc 100644
--- a/tildes/tildes/views/bookmarks.py
+++ b/tildes/tildes/views/bookmarks.py
@@ -1,6 +1,6 @@
"""Views relating to bookmarks."""
-from typing import Optional, Type, Union
+from typing import Optional, Union
from pyramid.request import Request
from pyramid.view import view_config
@@ -27,7 +27,7 @@ def get_bookmarks(
# pylint: disable=unused-argument
user = request.user
- bookmark_cls: Union[Type[CommentBookmark], Type[TopicBookmark]]
+ bookmark_cls: Union[type[CommentBookmark], type[TopicBookmark]]
if post_type == "comment":
post_cls = Comment
diff --git a/tildes/tildes/views/decorators.py b/tildes/tildes/views/decorators.py
index a697e71..2c41123 100644
--- a/tildes/tildes/views/decorators.py
+++ b/tildes/tildes/views/decorators.py
@@ -3,7 +3,8 @@
"""Contains decorators for view functions."""
-from typing import Any, Callable, Dict, Union
+from collections.abc import Callable
+from typing import Any, Union
from marshmallow import EXCLUDE
from marshmallow.fields import Field
@@ -15,7 +16,7 @@ from webargs import dict2schema, pyramidparser
def use_kwargs(
- argmap: Union[Schema, Dict[str, Field]], location: str = "query", **kwargs: Any
+ argmap: Union[Schema, dict[str, Field]], location: str = "query", **kwargs: Any
) -> Callable:
"""Wrap the webargs @use_kwargs decorator with preferred default modifications.
diff --git a/tildes/tildes/views/exceptions.py b/tildes/tildes/views/exceptions.py
index 8b424b7..f119f28 100644
--- a/tildes/tildes/views/exceptions.py
+++ b/tildes/tildes/views/exceptions.py
@@ -3,7 +3,7 @@
"""Views used by Pyramid when an exception is raised."""
-from typing import Sequence
+from collections.abc import Sequence
from urllib.parse import quote_plus
from marshmallow import ValidationError
diff --git a/tildes/tildes/views/financials.py b/tildes/tildes/views/financials.py
index 0ee33c4..3b50bfd 100644
--- a/tildes/tildes/views/financials.py
+++ b/tildes/tildes/views/financials.py
@@ -5,7 +5,7 @@
from collections import defaultdict
from decimal import Decimal
-from typing import Dict, List, Optional
+from typing import Optional
from pyramid.request import Request
from pyramid.view import view_config
@@ -30,7 +30,7 @@ def get_financials(request: Request) -> dict:
)
# split the entries up by type
- entries: Dict[str, List] = defaultdict(list)
+ entries = defaultdict(list)
for entry in financial_entries:
entries[entry.entry_type.name.lower()].append(entry)
@@ -43,7 +43,7 @@ def get_financials(request: Request) -> dict:
}
-def get_financial_data(db_session: Session) -> Optional[Dict[str, Decimal]]:
+def get_financial_data(db_session: Session) -> Optional[dict[str, Decimal]]:
"""Return financial data used to render the donation goal box."""
# get the total sum for each entry type in the financials table relevant to today
financial_totals = (
diff --git a/tildes/tildes/views/user.py b/tildes/tildes/views/user.py
index 2bc8162..71d4695 100644
--- a/tildes/tildes/views/user.py
+++ b/tildes/tildes/views/user.py
@@ -3,7 +3,7 @@
"""Views related to a specific user."""
-from typing import List, Optional, Type, Union
+from typing import Optional, Union
from enum import Enum
from marshmallow.fields import String
@@ -50,8 +50,8 @@ def get_user(
anchor_type = None
per_page = 20
- types_to_query: List[Union[Type[Topic], Type[Comment]]]
- order_options: Optional[Union[Type[TopicSortOption], Type[CommentSortOption]]]
+ types_to_query: list[Union[type[Topic], type[Comment]]]
+ order_options: Optional[Union[type[TopicSortOption], type[CommentSortOption]]]
if post_type == "topic":
types_to_query = [Topic]
@@ -111,8 +111,8 @@ def get_user_search(
"""Generate the search results page for a user's posts."""
user = request.context
- types_to_query: List[Union[Type[Topic], Type[Comment]]]
- order_options: Union[Type[TopicSortOption], Type[CommentSortOption]]
+ types_to_query: list[Union[type[Topic], type[Comment]]]
+ order_options: Union[type[TopicSortOption], type[CommentSortOption]]
if post_type == "topic":
types_to_query = [Topic]
@@ -170,7 +170,7 @@ def get_invite(request: Request) -> dict:
def _get_user_posts(
request: Request,
user: User,
- types_to_query: List[Union[Type[Topic], Type[Comment]]],
+ types_to_query: list[Union[type[Topic], type[Comment]]],
anchor_type: Optional[str],
before: Optional[str],
after: Optional[str],
diff --git a/tildes/tildes/views/votes.py b/tildes/tildes/views/votes.py
index 76a25f3..7f812ec 100644
--- a/tildes/tildes/views/votes.py
+++ b/tildes/tildes/views/votes.py
@@ -1,6 +1,6 @@
"""Views relating to voted posts."""
-from typing import Optional, Type, Union
+from typing import Optional, Union
from pyramid.request import Request
from pyramid.view import view_config
@@ -27,7 +27,7 @@ def get_voted_posts(
# pylint: disable=unused-argument
user = request.user
- vote_cls: Union[Type[CommentVote], Type[TopicVote]]
+ vote_cls: Union[type[CommentVote], type[TopicVote]]
if post_type == "comment":
post_cls = Comment