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