From 8144a8b789d79d4a74441c7933d143858b647f62 Mon Sep 17 00:00:00 2001 From: Deimos Date: Wed, 18 Nov 2020 15:33:31 -0700 Subject: [PATCH] Use postponed evaluation of type annotations The __future__ import will be able to be removed as of Python 3.10. --- tildes/tildes/lib/datetime.py | 3 +- tildes/tildes/lib/ratelimit.py | 7 +++-- .../comment/comment_notification_query.py | 7 +++-- tildes/tildes/models/comment/comment_query.py | 15 ++++----- tildes/tildes/models/group/group_query.py | 5 +-- tildes/tildes/models/model_query.py | 21 +++++++------ tildes/tildes/models/pagination.py | 13 ++++---- tildes/tildes/models/topic/topic.py | 7 +++-- tildes/tildes/models/topic/topic_query.py | 31 ++++++++++--------- 9 files changed, 59 insertions(+), 50 deletions(-) diff --git a/tildes/tildes/lib/datetime.py b/tildes/tildes/lib/datetime.py index 2d0f332..696d76a 100644 --- a/tildes/tildes/lib/datetime.py +++ b/tildes/tildes/lib/datetime.py @@ -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") diff --git a/tildes/tildes/lib/ratelimit.py b/tildes/tildes/lib/ratelimit.py index 2f67dca..abdfcdd 100644 --- a/tildes/tildes/lib/ratelimit.py +++ b/tildes/tildes/lib/ratelimit.py @@ -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 diff --git a/tildes/tildes/models/comment/comment_notification_query.py b/tildes/tildes/models/comment/comment_notification_query.py index 911e764..1c5fc12 100644 --- a/tildes/tildes/models/comment/comment_notification_query.py +++ b/tildes/tildes/models/comment/comment_notification_query.py @@ -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) diff --git a/tildes/tildes/models/comment/comment_query.py b/tildes/tildes/models/comment/comment_query.py index 620f63b..bcad415 100644 --- a/tildes/tildes/models/comment/comment_query.py +++ b/tildes/tildes/models/comment/comment_query.py @@ -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 diff --git a/tildes/tildes/models/group/group_query.py b/tildes/tildes/models/group/group_query.py index 0339d76..1c4ea77 100644 --- a/tildes/tildes/models/group/group_query.py +++ b/tildes/tildes/models/group/group_query.py @@ -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) diff --git a/tildes/tildes/models/model_query.py b/tildes/tildes/models/model_query.py index 1d944f0..2203772 100644 --- a/tildes/tildes/models/model_query.py +++ b/tildes/tildes/models/model_query.py @@ -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("*")) diff --git a/tildes/tildes/models/pagination.py b/tildes/tildes/models/pagination.py index 31cd0fb..aa92bd4 100644 --- a/tildes/tildes/models/pagination.py +++ b/tildes/tildes/models/pagination.py @@ -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) diff --git a/tildes/tildes/models/topic/topic.py b/tildes/tildes/models/topic/topic.py index 3e16c00..221d075 100644 --- a/tildes/tildes/models/topic/topic.py +++ b/tildes/tildes/models/topic/topic.py @@ -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'' @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 diff --git a/tildes/tildes/models/topic/topic_query.py b/tildes/tildes/models/topic/topic_query.py index 32897b9..debf499 100644 --- a/tildes/tildes/models/topic/topic_query.py +++ b/tildes/tildes/models/topic/topic_query.py @@ -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