mirror of
https://gitlab.com/tildes/tildes.git
synced 2026-04-16 06:18:34 +02:00
Type annotations: use standard generics (PEP 585)
As of Python 3.9, it's no longer necessary to import things like List and Dict from the typing module, and we can just use the built-in types like this.
This commit is contained in:
@@ -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 = (
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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 = []
|
||||
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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):
|
||||
<code class="highlight">...</code> instead (assumes a <pre> 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 <code> tag."""
|
||||
# pylint: disable=unused-argument
|
||||
yield (0, '<code class="highlight">')
|
||||
@@ -311,7 +301,7 @@ class LinkifyFilter(Filter):
|
||||
SUBREDDIT_REFERENCE_REGEX = re.compile(r"(?<!\w)/?r/(\w+)\b")
|
||||
|
||||
def __init__(
|
||||
self, source: NonRecursiveTreeWalker, skip_tags: Optional[List[str]] = None
|
||||
self, source: NonRecursiveTreeWalker, skip_tags: Optional[list[str]] = None
|
||||
):
|
||||
"""Initialize a linkification filter to apply to HTML.
|
||||
|
||||
@@ -385,8 +375,8 @@ class LinkifyFilter(Filter):
|
||||
|
||||
@staticmethod
|
||||
def _linkify_tokens(
|
||||
tokens: List[dict], filter_regex: Pattern, linkify_function: Callable
|
||||
) -> 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 <a>
|
||||
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 [
|
||||
{
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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"]
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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="{}"
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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]
|
||||
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user