Skip to content

Commit

Permalink
Merge cfe9957 into 63184d9
Browse files Browse the repository at this point in the history
  • Loading branch information
LilSpazJoekp committed Nov 12, 2021
2 parents 63184d9 + cfe9957 commit ac0cf18
Show file tree
Hide file tree
Showing 16 changed files with 697 additions and 402 deletions.
6 changes: 6 additions & 0 deletions CHANGES.rst
Expand Up @@ -14,6 +14,12 @@ Unreleased
- :meth:`.user_selectable` to get available subreddit link flairs.
- Automatic RateLimit handling will support errors with millisecond resolution.

**Deprecated**

- Ability to use :class:`.CommentForest` as an asynchronous iterator.
- :meth:`.CommentForest.list` no longer needs to be awaited.
- :attr:`.Submission.comments` no longer needs to be awaited and is now a property.

**Fixed**

- Fixed return value type of methods returning a listing in :class:`.Subreddit` and its
Expand Down
86 changes: 66 additions & 20 deletions asyncpraw/models/comment_forest.py
@@ -1,6 +1,8 @@
"""Provide CommentForest for Submission comments."""
import inspect
from heapq import heappop, heappush
from typing import TYPE_CHECKING, AsyncIterator, List, Optional, Union
from typing import TYPE_CHECKING, Any, AsyncIterator, Coroutine, List, Optional, Union
from warnings import warn

from ..exceptions import DuplicateReplaceException
from .reddit.more import MoreComments
Expand Down Expand Up @@ -41,19 +43,21 @@ def __getitem__(self, index: int):
.. code-block:: python
comments = await submission.comments()
first_comment = comments[0]
first_comment = submission.comments[0]
Alternatively, the presence of this method enables one to iterate over all top
level comments, like so:
.. code-block:: python
comments = await submission.comments()
for comment in comments:
for comment in submission.comments:
print(comment.body)
"""
if not (self._comments is not None or self._submission._fetched):
raise TypeError(
"Submission must be fetched before comments are accessible. Call `.load()` to fetch."
)
return self._comments[index]

async def __aiter__(self) -> AsyncIterator["asyncpraw.models.Comment"]:
Expand All @@ -63,22 +67,40 @@ async def __aiter__(self) -> AsyncIterator["asyncpraw.models.Comment"]:
.. code-block:: python
comments = await submission.comments()
comments = submission.comments
async for comment in comments:
print(comment.body)
"""
for comment in self._comments:
warn(
"Using CommentForest as an asynchronous iterator has been deprecated and"
" will be removed in a future version.",
category=DeprecationWarning,
stacklevel=3,
)
for comment in self:
yield comment

async def __call__(self): # noqa: D102
warn(
"`Submission.comments` is now a property and no longer needs to be awaited. This"
" will raise an error in a future version of Async PRAW.",
category=DeprecationWarning,
stacklevel=3,
)
if not self._submission._fetched:
await self._submission._fetch()
self._comments = self._submission.comments._comments
return self

def __init__(
self,
submission: "asyncpraw.models.Submission",
comments: Optional[List["asyncpraw.models.Comment"]] = None,
):
"""Initialize a CommentForest instance.
:param submission: An instance of :class:`~.Subreddit` that is the parent of the
:param submission: An instance of :class:`.Submission` that is the parent of the
comments.
:param comments: Initialize the Forest with a list of comments (default: None).
Expand All @@ -88,10 +110,7 @@ def __init__(

def __len__(self) -> int:
"""Return the number of top-level comments in the forest."""
if self._comments:
return len(self._comments)
else:
return 0
return len(self._comments or [])

def _insert_comment(self, comment):
if comment.name in self._submission._comments_by_id:
Expand All @@ -112,9 +131,16 @@ def _update(self, comments):
for comment in comments:
comment.submission = self._submission

async def list(
def list(
self,
) -> List[Union["asyncpraw.models.Comment", "asyncpraw.models.MoreComments"]]:
) -> Union[
List[Union["asyncpraw.models.Comment", "asyncpraw.models.MoreComments"]],
Coroutine[
Any,
Any,
List[Union["asyncpraw.models.Comment", "asyncpraw.models.MoreComments"]],
],
]:
"""Return a flattened list of all Comments.
This list may contain :class:`.MoreComments` instances if :meth:`.replace_more`
Expand All @@ -127,7 +153,28 @@ async def list(
comment = queue.pop(0)
comments.append(comment)
if not isinstance(comment, MoreComments):
queue.extend([reply async for reply in comment.replies])
queue.extend(comment.replies)
# check if this got called with await
# I'm so sorry this really gross
if any(
[
"await" in context
for context in inspect.getframeinfo(
inspect.currentframe().f_back
).code_context
]
):

async def async_func():
warn(
"`CommentForest.list()` no longer needs to be awaited and this"
" will raise an error in a future version of Async PRAW.",
category=DeprecationWarning,
stacklevel=3,
)
return comments

return async_func()
return comments

async def replace_more(
Expand All @@ -154,15 +201,15 @@ async def replace_more(
.. code-block:: python
submission = await reddit.submission("3hahrw", fetch=False)
comments = await submission.comments()
await comments.replace_more()
await submission.comments.replace_more()
Alternatively, to replace :class:`.MoreComments` instances within the replies of
a single comment try:
.. code-block:: python
comment = await reddit.comment("d8r4im1")
comment = await reddit.comment("d8r4im1", fetch=False)
await comment.refresh()
await comment.replies.replace_more()
.. note::
Expand All @@ -175,8 +222,7 @@ async def replace_more(
while True:
try:
comments = await submission.comments()
await comments.replace_more()
await submission.comments.replace_more()
break
except PossibleExceptions:
print("Handling replace_more exception")
Expand Down
28 changes: 13 additions & 15 deletions asyncpraw/models/reddit/comment.py
Expand Up @@ -13,6 +13,7 @@
UserContentMixin,
)
from .redditor import Redditor
from .submission import Submission
from .subreddit import Subreddit

if TYPE_CHECKING: # pragma: no cover
Expand Down Expand Up @@ -136,23 +137,21 @@ def replies(self) -> CommentForest:

@property
def submission(self) -> "asyncpraw.models.Submission":
"""Return the Submission object this comment belongs to."""
if not self._submission and self._fetched: # Comment not from submission
from .. import Submission
"""Return the Submission object this comment belongs to.
:raises: :py:class:`AttributeError` if the comment is not fetched.
"""
if not self._submission: # Comment not from submission
self._submission = Submission(
self._reddit, id=self._extract_submission_id()
)
return self._submission
elif self._submission:
return self._submission
else:
return None
return self._submission

@submission.setter
def submission(self, submission: "asyncpraw.models.Submission"):
"""Update the Submission associated with the Comment."""
submission._comments_by_id[self.name] = self
submission._comments_by_id[self.fullname] = self
self._submission = submission
# pylint: disable=not-an-iterable
for reply in self.replies:
Expand Down Expand Up @@ -280,9 +279,6 @@ async def parent(
if not self._fetched:
await self._fetch()

if not self.submission._fetched:
await self.submission._fetch()

if self.parent_id == self.submission.fullname:
return self.submission

Expand All @@ -298,6 +294,9 @@ async def parent(
async def refresh(self):
"""Refresh the comment's attributes.
If using :meth:`.Reddit.comment` with ``fetch=False``, this method must be
called in order to obtain the comment's replies.
Example usage:
.. code-block:: python
Expand All @@ -309,7 +308,7 @@ async def refresh(self):
if "context" in self.__dict__: # Using hasattr triggers a fetch
comment_path = self.context.split("?", 1)[0]
else:
if not self.submission:
if self._submission is None:
await self._fetch()
path = API_PATH["submission"].format(id=self.submission.id)
comment_path = f"{path}_/{self.id}"
Expand All @@ -320,8 +319,7 @@ async def refresh(self):
params["limit"] = self.reply_limit
if "reply_sort" in self.__dict__:
params["sort"] = self.reply_sort
response = await self._reddit.get(comment_path, params=params)
comment_list = response[1].children
comment_list = (await self._reddit.get(comment_path, params=params))[1].children
if not comment_list:
raise ClientException(self.MISSING_COMMENT_MESSAGE)

Expand Down
2 changes: 1 addition & 1 deletion asyncpraw/models/reddit/more.py
Expand Up @@ -44,7 +44,7 @@ async def _continue_comments(self, update):
parent = await self._load_comment(self.parent_id.split("_", 1)[1])
self._comments = parent.replies
if update:
async for comment in self._comments:
for comment in self._comments:
comment.submission = self.submission
return self._comments

Expand Down
85 changes: 38 additions & 47 deletions asyncpraw/models/reddit/submission.py
Expand Up @@ -495,48 +495,6 @@ def _kind(self) -> str:
"""Return the class's kind."""
return self._reddit.config.kinds["submission"]

async def comments(self) -> CommentForest:
"""Provide an instance of :class:`.CommentForest`.
This attribute can be used, for example, to obtain a flat list of comments, with
any :class:`.MoreComments` removed:
.. code-block:: python
comments = await submission.comments()
await comments.replace_more(limit=0)
comment_list = await comments.list()
for comment in comment_list:
# do stuff with comment
...
Sort order and comment limit can be set with the ``comment_sort`` and
``comment_limit`` attributes before comments are fetched, including any call to
:meth:`.replace_more`:
.. code-block:: python
submission.comment_sort = "new"
comments = await submission.comments()
comment_list = await comments.list()
for comment in comment_list:
# do stuff with comment
...
.. note::
The appropriate values for ``comment_sort`` include ``confidence``,
``controversial``, ``new``, ``old``, ``q&a``, and ``top``
See :ref:`extracting_comments` for more on working with a
:class:`.CommentForest`.
"""
# This assumes _comments is set so that _fetch is called when it's not.
if "_comments" not in self.__dict__:
await self._fetch()
return self._comments

@cachedproperty
def flair(self) -> SubmissionFlair:
"""Provide an instance of :class:`.SubmissionFlair`.
Expand Down Expand Up @@ -591,7 +549,7 @@ def __init__(
:param reddit: An instance of :class:`~.Reddit`.
:param id: A reddit base36 submission ID, e.g., ``2gmzqe``.
:param url: A URL supported by :meth:`~asyncpraw.models.Submission.id_from_url`.
:param url: A URL supported by :meth:`~.id_from_url`.
Either ``id`` or ``url`` can be provided, but not both.
Expand All @@ -611,6 +569,40 @@ def __init__(
super().__init__(reddit, _data=_data)

self._comments_by_id = {}
self.comments = CommentForest(self)
"""Provide an instance of :class:`.CommentForest`.
This attribute can be used, for example, to obtain a flat list of comments, with
any :class:`.MoreComments` removed:
.. code-block:: python
await submission.comments.replace_more(limit=0)
comments = submission.comments.list()
:raises: :py:class:`TypeError` if the submission is not fetched.
Sort order and comment limit must be set with the ``comment_sort`` and
``comment_limit`` attributes before the submission and its comments are fetched,
including any call to :meth:`.replace_more`. The ``fetch`` argument will need to
set when initializing the :class:`.Submission` instance:
.. code-block:: python
submission = await reddit.submission("8dmv8z", fetch=False)
submission.comment_sort = "new"
await submission.load()
comments = submission.comments.list()
.. note::
The appropriate values for ``comment_sort`` include ``confidence``,
``controversial``, ``new``, ``old``, ``q&a``, and ``top``
See :ref:`extracting_comments` for more on working with a
:class:`.CommentForest`.
"""

def __setattr__(self, attribute: str, value: Any):
"""Objectify author, subreddit, and poll data attributes."""
Expand Down Expand Up @@ -662,12 +654,11 @@ async def _fetch(self):
submission = type(self)(self._reddit, _data=submission_data)
delattr(submission, "comment_limit")
delattr(submission, "comment_sort")
submission._comments = CommentForest(self)
submission.comments = CommentForest(self)

self.__dict__.update(submission.__dict__)
self._comments._update(comment_listing.children)

self._fetched = True
self.comments._update(comment_listing.children)

async def mark_visited(self):
"""Mark submission as visited.
Expand Down Expand Up @@ -720,7 +711,7 @@ async def unhide(
.. code-block:: python
submission = await reddit.submission(id="5or86n")
submission = await reddit.submission(id="5or86n", fetch=False)
await submission.unhide()
.. seealso::
Expand Down

0 comments on commit ac0cf18

Please sign in to comment.