mirror of
https://gitlab.com/tildes/tildes.git
synced 2026-04-17 14:59:11 +02:00
Use postponed evaluation of type annotations
The __future__ import will be able to be removed as of Python 3.10.
This commit is contained in:
@@ -3,6 +3,7 @@
|
||||
|
||||
"""Functions/classes related to dates and times."""
|
||||
|
||||
from __future__ import annotations
|
||||
import re
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from typing import Any, Optional
|
||||
@@ -30,7 +31,7 @@ class SimpleHoursPeriod:
|
||||
raise ValueError("Time period is too large")
|
||||
|
||||
@classmethod
|
||||
def from_short_form(cls, short_form: str) -> "SimpleHoursPeriod":
|
||||
def from_short_form(cls, short_form: str) -> SimpleHoursPeriod:
|
||||
"""Initialize a period from a "short form" string (e.g. "2h", "4d")."""
|
||||
if not cls._SHORT_FORM_REGEX.match(short_form):
|
||||
raise ValueError("Invalid time period")
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
"""Classes and constants related to rate-limited actions."""
|
||||
|
||||
from __future__ import annotations
|
||||
from datetime import timedelta
|
||||
from ipaddress import ip_address
|
||||
from typing import Any, List, Optional, Sequence
|
||||
@@ -58,7 +59,7 @@ class RateLimitResult:
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def unlimited_result(cls) -> "RateLimitResult":
|
||||
def unlimited_result(cls) -> RateLimitResult:
|
||||
"""Return a "blank" result representing an unlimited action."""
|
||||
return cls(
|
||||
is_allowed=True,
|
||||
@@ -68,7 +69,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:
|
||||
@@ -98,7 +99,7 @@ class RateLimitResult:
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def merged_result(cls, results: Sequence["RateLimitResult"]) -> "RateLimitResult":
|
||||
def merged_result(cls, results: Sequence["RateLimitResult"]) -> RateLimitResult:
|
||||
"""Merge any number of RateLimitResults into a single result.
|
||||
|
||||
Basically, the merged result should be the "most restrictive" combination of all
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
"""Contains the CommentNotificationQuery class."""
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import Any
|
||||
|
||||
from pyramid.request import Request
|
||||
@@ -32,7 +33,7 @@ class CommentNotificationQuery(PaginatedQuery):
|
||||
.subquery()
|
||||
)
|
||||
|
||||
def _attach_extra_data(self) -> "CommentNotificationQuery":
|
||||
def _attach_extra_data(self) -> CommentNotificationQuery:
|
||||
"""Attach the user's comment votes to the query."""
|
||||
vote_subquery = (
|
||||
self.request.query(CommentVote)
|
||||
@@ -45,7 +46,7 @@ class CommentNotificationQuery(PaginatedQuery):
|
||||
)
|
||||
return self.add_columns(vote_subquery)
|
||||
|
||||
def join_all_relationships(self) -> "CommentNotificationQuery":
|
||||
def join_all_relationships(self) -> CommentNotificationQuery:
|
||||
"""Eagerly join the comment, topic, and group to the notification."""
|
||||
# pylint: disable=self-cls-assignment
|
||||
self = self.options(
|
||||
@@ -69,7 +70,7 @@ class CommentNotificationQuery(PaginatedQuery):
|
||||
|
||||
return notification
|
||||
|
||||
def get_page(self, per_page: int) -> "CommentNotificationResults":
|
||||
def get_page(self, per_page: int) -> CommentNotificationResults:
|
||||
"""Get a page worth of results from the query (`per page` items)."""
|
||||
return CommentNotificationResults(self, per_page)
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
"""Contains the CommentQuery class."""
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import Any
|
||||
|
||||
from pyramid.request import Request
|
||||
@@ -31,7 +32,7 @@ class CommentQuery(PaginatedQuery):
|
||||
self._only_bookmarked = False
|
||||
self._only_user_voted = False
|
||||
|
||||
def _attach_extra_data(self) -> "CommentQuery":
|
||||
def _attach_extra_data(self) -> CommentQuery:
|
||||
"""Attach the extra user data to the query."""
|
||||
# pylint: disable=protected-access
|
||||
if not self.request.user:
|
||||
@@ -39,7 +40,7 @@ class CommentQuery(PaginatedQuery):
|
||||
|
||||
return self._attach_vote_data()._attach_bookmark_data()
|
||||
|
||||
def _attach_vote_data(self) -> "CommentQuery":
|
||||
def _attach_vote_data(self) -> CommentQuery:
|
||||
"""Join the data related to whether the user has voted on the comment."""
|
||||
query = self.join(
|
||||
CommentVote,
|
||||
@@ -53,7 +54,7 @@ class CommentQuery(PaginatedQuery):
|
||||
|
||||
return query
|
||||
|
||||
def _attach_bookmark_data(self) -> "CommentQuery":
|
||||
def _attach_bookmark_data(self) -> CommentQuery:
|
||||
"""Join the data related to whether the user has bookmarked the comment."""
|
||||
query = self.join(
|
||||
CommentBookmark,
|
||||
@@ -86,7 +87,7 @@ class CommentQuery(PaginatedQuery):
|
||||
|
||||
def apply_sort_option(
|
||||
self, sort: CommentSortOption, desc: bool = True
|
||||
) -> "CommentQuery":
|
||||
) -> CommentQuery:
|
||||
"""Apply a CommentSortOption sorting method (generative)."""
|
||||
if sort == CommentSortOption.VOTES:
|
||||
self._sort_column = Comment.num_votes
|
||||
@@ -97,18 +98,18 @@ class CommentQuery(PaginatedQuery):
|
||||
|
||||
return self
|
||||
|
||||
def search(self, query: str) -> "CommentQuery":
|
||||
def search(self, query: str) -> CommentQuery:
|
||||
"""Restrict the comments to ones that match a search query (generative)."""
|
||||
return self.filter(
|
||||
Comment.search_tsv.op("@@")(func.websearch_to_tsquery(query))
|
||||
)
|
||||
|
||||
def only_bookmarked(self) -> "CommentQuery":
|
||||
def only_bookmarked(self) -> CommentQuery:
|
||||
"""Restrict the comments to ones that the user has bookmarked (generative)."""
|
||||
self._only_bookmarked = True
|
||||
return self
|
||||
|
||||
def only_user_voted(self) -> "CommentQuery":
|
||||
def only_user_voted(self) -> CommentQuery:
|
||||
"""Restrict the comments to ones that the user has voted on (generative)."""
|
||||
self._only_user_voted = True
|
||||
return self
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
"""Contains the GroupQuery class."""
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import Any
|
||||
|
||||
from pyramid.request import Request
|
||||
@@ -24,14 +25,14 @@ class GroupQuery(ModelQuery):
|
||||
"""
|
||||
super().__init__(Group, request)
|
||||
|
||||
def _attach_extra_data(self) -> "GroupQuery":
|
||||
def _attach_extra_data(self) -> GroupQuery:
|
||||
"""Attach the extra user data to the query."""
|
||||
if not self.request.user:
|
||||
return self
|
||||
|
||||
return self._attach_subscription_data()
|
||||
|
||||
def _attach_subscription_data(self) -> "GroupQuery":
|
||||
def _attach_subscription_data(self) -> GroupQuery:
|
||||
"""Add a subquery to include whether the user is subscribed."""
|
||||
subscription_subquery = (
|
||||
self.request.query(GroupSubscription)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
"""Contains the ModelQuery class, a specialized SQLAlchemy Query subclass."""
|
||||
# pylint: disable=self-cls-assignment
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import Any, Iterator, TypeVar
|
||||
|
||||
from pyramid.request import Request
|
||||
@@ -40,11 +41,11 @@ class ModelQuery(Query):
|
||||
results = super().__iter__()
|
||||
return iter([self._process_result(result) for result in results])
|
||||
|
||||
def _attach_extra_data(self) -> "ModelQuery":
|
||||
def _attach_extra_data(self) -> ModelQuery:
|
||||
"""Override to attach extra data to query before execution."""
|
||||
return self
|
||||
|
||||
def _finalize(self) -> "ModelQuery":
|
||||
def _finalize(self) -> ModelQuery:
|
||||
"""Finalize the query before it's executed."""
|
||||
# pylint: disable=protected-access
|
||||
|
||||
@@ -59,7 +60,7 @@ class ModelQuery(Query):
|
||||
._filter_removed_if_necessary()
|
||||
)
|
||||
|
||||
def _before_compile_listener(self) -> "ModelQuery":
|
||||
def _before_compile_listener(self) -> ModelQuery:
|
||||
"""Do any final adjustments to the query before it's compiled.
|
||||
|
||||
Note that this method cannot be overridden by subclasses because of the way it
|
||||
@@ -68,21 +69,21 @@ class ModelQuery(Query):
|
||||
"""
|
||||
return self._finalize()
|
||||
|
||||
def _filter_deleted_if_necessary(self) -> "ModelQuery":
|
||||
def _filter_deleted_if_necessary(self) -> ModelQuery:
|
||||
"""Filter out deleted rows unless they were explicitly included."""
|
||||
if not self.filter_deleted:
|
||||
return self
|
||||
|
||||
return self.filter(self.model_cls.is_deleted == False) # noqa
|
||||
|
||||
def _filter_removed_if_necessary(self) -> "ModelQuery":
|
||||
def _filter_removed_if_necessary(self) -> ModelQuery:
|
||||
"""Filter out removed rows unless they were explicitly included."""
|
||||
if not self.filter_removed:
|
||||
return self
|
||||
|
||||
return self.filter(self.model_cls.is_removed == False) # noqa
|
||||
|
||||
def lock_based_on_request_method(self) -> "ModelQuery":
|
||||
def lock_based_on_request_method(self) -> ModelQuery:
|
||||
"""Lock the rows if request method implies it's needed (generative).
|
||||
|
||||
Applying this function to a query will cause the database to acquire a row-level
|
||||
@@ -98,19 +99,19 @@ class ModelQuery(Query):
|
||||
|
||||
return self
|
||||
|
||||
def include_deleted(self) -> "ModelQuery":
|
||||
def include_deleted(self) -> ModelQuery:
|
||||
"""Specify that deleted rows should be included (generative)."""
|
||||
self.filter_deleted = False
|
||||
|
||||
return self
|
||||
|
||||
def include_removed(self) -> "ModelQuery":
|
||||
def include_removed(self) -> ModelQuery:
|
||||
"""Specify that removed rows should be included (generative)."""
|
||||
self.filter_removed = False
|
||||
|
||||
return self
|
||||
|
||||
def join_all_relationships(self) -> "ModelQuery":
|
||||
def join_all_relationships(self) -> ModelQuery:
|
||||
"""Eagerly join all lazy relationships (generative).
|
||||
|
||||
This is useful for being able to load an item "fully" in a single query and
|
||||
@@ -120,7 +121,7 @@ class ModelQuery(Query):
|
||||
|
||||
return self
|
||||
|
||||
def undefer_all_columns(self) -> "ModelQuery":
|
||||
def undefer_all_columns(self) -> ModelQuery:
|
||||
"""Undefer all columns (generative)."""
|
||||
self = self.options(undefer("*"))
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
"""Contains the PaginatedQuery and PaginatedResults classes."""
|
||||
|
||||
from __future__ import annotations
|
||||
from itertools import chain
|
||||
from typing import Any, Iterator, List, Optional, Sequence, TypeVar
|
||||
|
||||
@@ -85,14 +86,14 @@ class PaginatedQuery(ModelQuery):
|
||||
"""
|
||||
return bool(self.before_id)
|
||||
|
||||
def anchor_type(self, anchor_type: str) -> "PaginatedQuery":
|
||||
def anchor_type(self, anchor_type: str) -> PaginatedQuery:
|
||||
"""Set the type of the "anchor" (before/after item) (generative)."""
|
||||
anchor_table_name = anchor_type + "s"
|
||||
self._anchor_table = self.model_cls.metadata.tables.get(anchor_table_name)
|
||||
|
||||
return self
|
||||
|
||||
def after_id36(self, id36: str) -> "PaginatedQuery":
|
||||
def after_id36(self, id36: str) -> PaginatedQuery:
|
||||
"""Restrict the query to results after an id36 (generative)."""
|
||||
if self.before_id:
|
||||
raise ValueError("Can't set both before and after restrictions")
|
||||
@@ -101,7 +102,7 @@ class PaginatedQuery(ModelQuery):
|
||||
|
||||
return self
|
||||
|
||||
def before_id36(self, id36: str) -> "PaginatedQuery":
|
||||
def before_id36(self, id36: str) -> PaginatedQuery:
|
||||
"""Restrict the query to results before an id36 (generative)."""
|
||||
if self.after_id:
|
||||
raise ValueError("Can't set both before and after restrictions")
|
||||
@@ -110,7 +111,7 @@ class PaginatedQuery(ModelQuery):
|
||||
|
||||
return self
|
||||
|
||||
def _apply_before_or_after(self) -> "PaginatedQuery":
|
||||
def _apply_before_or_after(self) -> PaginatedQuery:
|
||||
"""Apply the "before" or "after" restrictions if necessary."""
|
||||
# pylint: disable=assignment-from-no-return
|
||||
if not (self.after_id or self.before_id):
|
||||
@@ -165,7 +166,7 @@ class PaginatedQuery(ModelQuery):
|
||||
.subquery()
|
||||
)
|
||||
|
||||
def _finalize(self) -> "PaginatedQuery":
|
||||
def _finalize(self) -> PaginatedQuery:
|
||||
"""Finalize the query before execution."""
|
||||
query = super()._finalize()
|
||||
|
||||
@@ -185,7 +186,7 @@ class PaginatedQuery(ModelQuery):
|
||||
|
||||
return query
|
||||
|
||||
def get_page(self, per_page: int) -> "PaginatedResults":
|
||||
def get_page(self, per_page: int) -> PaginatedResults:
|
||||
"""Get a page worth of results from the query (`per page` items)."""
|
||||
return PaginatedResults(self, per_page)
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
"""Contains the Topic class."""
|
||||
|
||||
from __future__ import annotations
|
||||
from datetime import datetime, timedelta
|
||||
from itertools import chain
|
||||
from pathlib import PurePosixPath
|
||||
@@ -217,7 +218,7 @@ class Topic(DatabaseModel):
|
||||
return f'<Topic "{self.title}" ({self.topic_id})>'
|
||||
|
||||
@classmethod
|
||||
def _create_base_topic(cls, group: Group, author: User, title: str) -> "Topic":
|
||||
def _create_base_topic(cls, group: Group, author: User, title: str) -> Topic:
|
||||
"""Create the "base" for a new topic."""
|
||||
new_topic = cls()
|
||||
new_topic.group = group
|
||||
@@ -234,7 +235,7 @@ class Topic(DatabaseModel):
|
||||
@classmethod
|
||||
def create_text_topic(
|
||||
cls, group: Group, author: User, title: str, markdown: str = ""
|
||||
) -> "Topic":
|
||||
) -> Topic:
|
||||
"""Create a new text topic."""
|
||||
new_topic = cls._create_base_topic(group, author, title)
|
||||
new_topic.topic_type = TopicType.TEXT
|
||||
@@ -245,7 +246,7 @@ class Topic(DatabaseModel):
|
||||
@classmethod
|
||||
def create_link_topic(
|
||||
cls, group: Group, author: User, title: str, link: str
|
||||
) -> "Topic":
|
||||
) -> Topic:
|
||||
"""Create a new link topic."""
|
||||
new_topic = cls._create_base_topic(group, author, title)
|
||||
new_topic.topic_type = TopicType.LINK
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
"""Contains the TopicQuery class."""
|
||||
|
||||
from __future__ import annotations
|
||||
from typing import Any, Sequence
|
||||
|
||||
from pyramid.request import Request
|
||||
@@ -40,7 +41,7 @@ class TopicQuery(PaginatedQuery):
|
||||
|
||||
self.filter_ignored = False
|
||||
|
||||
def _attach_extra_data(self) -> "TopicQuery":
|
||||
def _attach_extra_data(self) -> TopicQuery:
|
||||
"""Attach the extra user data to the query."""
|
||||
if not self.request.user:
|
||||
return self
|
||||
@@ -53,7 +54,7 @@ class TopicQuery(PaginatedQuery):
|
||||
._attach_ignored_data()
|
||||
)
|
||||
|
||||
def _finalize(self) -> "TopicQuery":
|
||||
def _finalize(self) -> TopicQuery:
|
||||
"""Finalize the query before it's executed."""
|
||||
# pylint: disable=self-cls-assignment
|
||||
self = super()._finalize()
|
||||
@@ -63,7 +64,7 @@ class TopicQuery(PaginatedQuery):
|
||||
|
||||
return self
|
||||
|
||||
def _attach_vote_data(self) -> "TopicQuery":
|
||||
def _attach_vote_data(self) -> TopicQuery:
|
||||
"""Join the data related to whether the user has voted on the topic."""
|
||||
query = self.join(
|
||||
TopicVote,
|
||||
@@ -77,7 +78,7 @@ class TopicQuery(PaginatedQuery):
|
||||
|
||||
return query
|
||||
|
||||
def _attach_bookmark_data(self) -> "TopicQuery":
|
||||
def _attach_bookmark_data(self) -> TopicQuery:
|
||||
"""Join the data related to whether the user has bookmarked the topic."""
|
||||
query = self.join(
|
||||
TopicBookmark,
|
||||
@@ -91,7 +92,7 @@ class TopicQuery(PaginatedQuery):
|
||||
|
||||
return query
|
||||
|
||||
def _attach_visit_data(self) -> "TopicQuery":
|
||||
def _attach_visit_data(self) -> TopicQuery:
|
||||
"""Join the data related to the user's last visit to the topic(s)."""
|
||||
# subquery using LATERAL to select only the newest visit for each topic
|
||||
lateral_subquery = (
|
||||
@@ -116,7 +117,7 @@ class TopicQuery(PaginatedQuery):
|
||||
|
||||
return query
|
||||
|
||||
def _attach_ignored_data(self) -> "TopicQuery":
|
||||
def _attach_ignored_data(self) -> TopicQuery:
|
||||
"""Join the data related to whether the user has ignored the topic."""
|
||||
query = self.join(
|
||||
TopicIgnore,
|
||||
@@ -160,7 +161,7 @@ class TopicQuery(PaginatedQuery):
|
||||
|
||||
def apply_sort_option(
|
||||
self, sort: TopicSortOption, is_desc: bool = True
|
||||
) -> "TopicQuery":
|
||||
) -> TopicQuery:
|
||||
"""Apply a TopicSortOption sorting method (generative)."""
|
||||
if sort == TopicSortOption.VOTES:
|
||||
self._sort_column = Topic.num_votes
|
||||
@@ -179,7 +180,7 @@ class TopicQuery(PaginatedQuery):
|
||||
|
||||
def inside_groups(
|
||||
self, groups: Sequence[Group], include_subgroups: bool = True
|
||||
) -> "TopicQuery":
|
||||
) -> TopicQuery:
|
||||
"""Restrict the topics to inside specific groups (generative)."""
|
||||
if include_subgroups:
|
||||
query_paths = [group.path for group in groups]
|
||||
@@ -191,7 +192,7 @@ class TopicQuery(PaginatedQuery):
|
||||
|
||||
return self.filter(Topic.group_id.in_(group_ids)) # type: ignore
|
||||
|
||||
def inside_time_period(self, period: SimpleHoursPeriod) -> "TopicQuery":
|
||||
def inside_time_period(self, period: SimpleHoursPeriod) -> TopicQuery:
|
||||
"""Restrict the topics to inside a time period (generative)."""
|
||||
# if the time period is too long, this will crash by creating a datetime outside
|
||||
# the valid range - catch that and just don't filter by time period at all if
|
||||
@@ -203,7 +204,7 @@ class TopicQuery(PaginatedQuery):
|
||||
|
||||
return self.filter(Topic.created_time > start_time)
|
||||
|
||||
def has_tag(self, tag: str) -> "TopicQuery":
|
||||
def has_tag(self, tag: str) -> TopicQuery:
|
||||
"""Restrict the topics to ones with a specific tag (generative).
|
||||
|
||||
Note that this method searches for topics that have any tag that contains
|
||||
@@ -214,7 +215,7 @@ class TopicQuery(PaginatedQuery):
|
||||
# pylint: disable=protected-access
|
||||
return self.filter(Topic.tags.lquery(query)) # type: ignore
|
||||
|
||||
def search(self, query: str) -> "TopicQuery":
|
||||
def search(self, query: str) -> TopicQuery:
|
||||
"""Restrict the topics to ones that match a search query (generative)."""
|
||||
# Replace "." with space, since tags are stored as space-separated strings
|
||||
# in the search index.
|
||||
@@ -223,24 +224,24 @@ class TopicQuery(PaginatedQuery):
|
||||
|
||||
return self.filter(Topic.search_tsv.op("@@")(func.websearch_to_tsquery(query)))
|
||||
|
||||
def only_bookmarked(self) -> "TopicQuery":
|
||||
def only_bookmarked(self) -> TopicQuery:
|
||||
"""Restrict the topics to ones that the user has bookmarked (generative)."""
|
||||
self._only_bookmarked = True
|
||||
return self
|
||||
|
||||
def only_user_voted(self) -> "TopicQuery":
|
||||
def only_user_voted(self) -> TopicQuery:
|
||||
"""Restrict the topics to ones that the user has voted on (generative)."""
|
||||
self._only_user_voted = True
|
||||
return self
|
||||
|
||||
def only_ignored(self) -> "TopicQuery":
|
||||
def only_ignored(self) -> TopicQuery:
|
||||
"""Restrict the topics to ones that the user has ignored (generative)."""
|
||||
# pylint: disable=self-cls-assignment
|
||||
self._only_ignored = True
|
||||
|
||||
return self
|
||||
|
||||
def exclude_ignored(self) -> "TopicQuery":
|
||||
def exclude_ignored(self) -> TopicQuery:
|
||||
"""Specify that ignored topics should be excluded (generative)."""
|
||||
self.filter_ignored = True
|
||||
|
||||
|
||||
Reference in New Issue
Block a user