[Python-modules-commits] [praw] 11/14: Import praw_5.2.0.orig.tar
Josué Ortega
josue at moszumanska.debian.org
Sat Oct 28 21:13:05 UTC 2017
This is an automated email from the git hooks/post-receive script.
josue pushed a commit to branch master
in repository praw.
commit dd1064876c271e9cf79c8534967bb94e1ca5a3f8
Author: Josue Ortega <josue at debian.org>
Date: Sat Oct 28 14:14:07 2017 -0600
Import praw_5.2.0.orig.tar
---
CHANGES.rst | 50 +
PKG-INFO | 2 +-
docs/code_overview/other.rst | 1 +
docs/code_overview/other/redditorstream.rst | 5 +
docs/tutorials/comments.rst | 2 +-
praw.egg-info/PKG-INFO | 2 +-
praw.egg-info/SOURCES.txt | 18 +-
praw.egg-info/requires.txt | 4 +-
praw/const.py | 5 +-
praw/exceptions.py | 12 +-
praw/models/comment_forest.py | 2 +-
praw/models/inbox.py | 56 +
praw/models/listing/generator.py | 2 +-
praw/models/listing/mixins/submission.py | 4 +-
praw/models/listing/mixins/subreddit.py | 17 +-
praw/models/reddit/comment.py | 80 +-
praw/models/reddit/live.py | 46 +-
praw/models/reddit/mixins/inboxable.py | 18 +
praw/models/reddit/more.py | 2 +-
praw/models/reddit/redditor.py | 82 +-
praw/models/reddit/submission.py | 24 +-
praw/models/reddit/subreddit.py | 34 +-
praw/models/reddit/wikipage.py | 6 +-
praw/models/util.py | 2 +-
praw/objector.py | 2 +-
praw/reddit.py | 2 +-
setup.py | 4 +-
tests/conftest.py | 3 +-
.../cassettes/TestComment.test_parent__chain.json | 386 +++
.../TestComment.test_parent__comment.json | 6 +-
.../cassettes/TestComment.test_permalink.json | 149 -
.../cassettes/TestComment.test_refresh.json | 6 +-
.../TestComment.test_refresh__deleted_comment.json | 6 +-
...estComment.test_refresh__raises_exception.json} | 8 +-
.../TestComment.test_refresh__removed_comment.json | 166 +
.../cassettes/TestComment.test_refresh__twice.json | 221 ++
...mentForest.test_replace__on_direct_comment.json | 6 +-
.../TestInbox.test_comment_reply__refresh.json | 60 +-
.../cassettes/TestInbox.test_mention__refresh.json | 6 +-
.../cassettes/TestInbox.test_message_collapse.json | 341 ++
.../TestInbox.test_message_uncollapse.json | 341 ++
.../cassettes/TestLiveUpdate_test_attributes.json | 109 +
.../TestMessage.test_message_collapse.json | 170 +
.../TestMessage.test_message_uncollapse.json | 170 +
.../TestRedditor.test_stream__comments.json | 330 ++
.../TestRedditor.test_stream__submissions.json | 3465 ++++++++++++++++++++
.../cassettes/TestSubmission.test_crosspost.json | 227 ++
...estSubmission.test_crosspost__custom_title.json | 227 ++
...ubmission.test_crosspost__subreddit_object.json | 227 ++
.../cassettes/TestSubmission.test_gilded.json | 111 +
...bredditStylesheet.test_upload__invalid_ext.json | 113 +
tests/integration/models/reddit/test_comment.py | 43 +-
tests/integration/models/reddit/test_live.py | 13 +
tests/integration/models/reddit/test_message.py | 18 +-
tests/integration/models/reddit/test_redditor.py | 16 +
tests/integration/models/reddit/test_submission.py | 44 +
tests/integration/models/reddit/test_subreddit.py | 10 +
tests/integration/models/test_inbox.py | 18 +
tests/unit/models/reddit/test_live.py | 2 -
tests/unit/test_exceptions.py | 9 +
60 files changed, 7200 insertions(+), 311 deletions(-)
diff --git a/CHANGES.rst b/CHANGES.rst
index 13ee05c..f5a9037 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -1,6 +1,56 @@
Change Log
==========
+5.2.0 (2017/10/24)
+------------------
+
+**Changed**
+
+* An attribute on :class:`.LiveUpdate` now works as lazy attribute (i.e.
+ populate an attribute when the attribute is first accessed).
+
+**Deprecated**
+
+* ``subreddit.comments.gilded`` because there isn't actually an endpoint that
+ returns only gilded comments. Use ``subreddit.gilded`` instead.
+
+**Fixed**
+
+* Removed ``comment.permalink()`` because ``comment.permalink`` is now an
+ attribute returned by Reddit.
+
+
+5.1.0 (2017/08/31)
+------------------
+
+**Added**
+
+* :attr:`.Redditor.stream`, with methods :meth:`.RedditorStream.submissions()`
+ and :meth:`.RedditorStream.comments()` to stream a Redditor's
+ comments or submissions
+* :class:`.RedditorStream` has been added to facilitate
+ :attr:`.Redditor.stream`
+* :meth:`.Inbox.collapse` to mark messages as collapsed.
+* :meth:`.Inbox.uncollapse` to mark messages as uncollapsed.
+* Raise :class:`.ClientException` when calling :meth:`.refresh` when the
+ comment does not appear in the resulting comment tree.
+* :meth:`.Submission.crosspost` to crosspost to a subreddit.
+
+**Fixed**
+
+* Calling :meth:`.refresh` on a directly fetched, deeply nested
+ :class:`.Comment` will additionally pull in as many parent comments as
+ possible (currently 8) enabling significantly quicker traversal to the
+ top-most :class:`.Comment` via successive :meth:`.parent()` calls.
+* Calling :meth:`.refresh` previously could have resulted in a
+ ``AttributeError: 'MoreComments' object has no attribute '_replies'``
+ exception. This situation will now result in a :class:`.ClientException`.
+* Properly handle ``BAD_CSS_NAME`` errors when uploading stylesheet images with
+ invalid filenames. Previously an ``AssertionError`` was raised.
+* :class:`.Submission`'s ``gilded`` attribute properly returns the expected
+ value from reddit.
+
+
5.0.1 (2017/07/11)
------------------
diff --git a/PKG-INFO b/PKG-INFO
index edb1cf1..c4f3ed8 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: praw
-Version: 5.0.1
+Version: 5.2.0
Summary: PRAW, an acronym for `Python Reddit API Wrapper`, is a python package that allows for simple access to reddit's API.
Home-page: https://praw.readthedocs.org/
Author: Bryce Boe
diff --git a/docs/code_overview/other.rst b/docs/code_overview/other.rst
index 5a81f97..e1afe16 100644
--- a/docs/code_overview/other.rst
+++ b/docs/code_overview/other.rst
@@ -65,4 +65,5 @@ instances of them bound to an attribute of one of the PRAW models.
other/redditorlist
other/sublisting
other/subredditmessage
+ other/redditorstream
other/util
diff --git a/docs/code_overview/other/redditorstream.rst b/docs/code_overview/other/redditorstream.rst
new file mode 100644
index 0000000..36a153c
--- /dev/null
+++ b/docs/code_overview/other/redditorstream.rst
@@ -0,0 +1,5 @@
+RedditorStream
+==============
+
+.. autoclass:: praw.models.reddit.redditor.RedditorStream
+ :inherited-members:
diff --git a/docs/tutorials/comments.rst b/docs/tutorials/comments.rst
index 2c5af20..68ba5f1 100644
--- a/docs/tutorials/comments.rst
+++ b/docs/tutorials/comments.rst
@@ -131,7 +131,7 @@ order as the code above. Thus the above can be rewritten as:
for comment in submission.comments.list():
print(comment.body)
-Now you can now properly extract and parse all (or most) of the comments
+You can now properly extract and parse all (or most) of the comments
belonging to a single submission. Combine this with :ref:`submission iteration
<submission-iteration>` and you can build some really cool stuff.
diff --git a/praw.egg-info/PKG-INFO b/praw.egg-info/PKG-INFO
index edb1cf1..c4f3ed8 100644
--- a/praw.egg-info/PKG-INFO
+++ b/praw.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
Metadata-Version: 1.1
Name: praw
-Version: 5.0.1
+Version: 5.2.0
Summary: PRAW, an acronym for `Python Reddit API Wrapper`, is a python package that allows for simple access to reddit's API.
Home-page: https://praw.readthedocs.org/
Author: Bryce Boe
diff --git a/praw.egg-info/SOURCES.txt b/praw.egg-info/SOURCES.txt
index 20f792c..967358c 100644
--- a/praw.egg-info/SOURCES.txt
+++ b/praw.egg-info/SOURCES.txt
@@ -52,6 +52,7 @@ docs/code_overview/other/modmail.rst
docs/code_overview/other/modmailmessage.rst
docs/code_overview/other/redditbase.rst
docs/code_overview/other/redditorlist.rst
+docs/code_overview/other/redditorstream.rst
docs/code_overview/other/sublisting.rst
docs/code_overview/other/submissionflair.rst
docs/code_overview/other/submissionmoderation.rst
@@ -177,12 +178,15 @@ tests/integration/cassettes/TestComment.test_gild__no_creddits.json
tests/integration/cassettes/TestComment.test_invalid.json
tests/integration/cassettes/TestComment.test_mark_read.json
tests/integration/cassettes/TestComment.test_mark_unread.json
+tests/integration/cassettes/TestComment.test_parent__chain.json
tests/integration/cassettes/TestComment.test_parent__comment.json
tests/integration/cassettes/TestComment.test_parent__comment_from_forest.json
tests/integration/cassettes/TestComment.test_parent__submission.json
-tests/integration/cassettes/TestComment.test_permalink.json
tests/integration/cassettes/TestComment.test_refresh.json
tests/integration/cassettes/TestComment.test_refresh__deleted_comment.json
+tests/integration/cassettes/TestComment.test_refresh__raises_exception.json
+tests/integration/cassettes/TestComment.test_refresh__removed_comment.json
+tests/integration/cassettes/TestComment.test_refresh__twice.json
tests/integration/cassettes/TestComment.test_reply.json
tests/integration/cassettes/TestComment.test_report.json
tests/integration/cassettes/TestComment.test_save.json
@@ -225,6 +229,8 @@ tests/integration/cassettes/TestInbox.test_mention__refresh.json
tests/integration/cassettes/TestInbox.test_mentions.json
tests/integration/cassettes/TestInbox.test_message.json
tests/integration/cassettes/TestInbox.test_message__unauthorized.json
+tests/integration/cassettes/TestInbox.test_message_collapse.json
+tests/integration/cassettes/TestInbox.test_message_uncollapse.json
tests/integration/cassettes/TestInbox.test_messages.json
tests/integration/cassettes/TestInbox.test_sent.json
tests/integration/cassettes/TestInbox.test_submission_replies.json
@@ -262,10 +268,13 @@ tests/integration/cassettes/TestLiveThread_test_report.json
tests/integration/cassettes/TestLiveThread_test_updates.json
tests/integration/cassettes/TestLiveUpdateContribution_remove.json
tests/integration/cassettes/TestLiveUpdateContribution_strike.json
+tests/integration/cassettes/TestLiveUpdate_test_attributes.json
tests/integration/cassettes/TestMessage.test_attributes.json
tests/integration/cassettes/TestMessage.test_block.json
tests/integration/cassettes/TestMessage.test_mark_read.json
tests/integration/cassettes/TestMessage.test_mark_unread.json
+tests/integration/cassettes/TestMessage.test_message_collapse.json
+tests/integration/cassettes/TestMessage.test_message_uncollapse.json
tests/integration/cassettes/TestMessage.test_reply.json
tests/integration/cassettes/TestModmailConversation.test_archive.json
tests/integration/cassettes/TestModmailConversation.test_highlight.json
@@ -316,6 +325,8 @@ tests/integration/cassettes/TestRedditor.test_gild__no_creddits.json
tests/integration/cassettes/TestRedditor.test_message.json
tests/integration/cassettes/TestRedditor.test_message_from_subreddit.json
tests/integration/cassettes/TestRedditor.test_multireddits.json
+tests/integration/cassettes/TestRedditor.test_stream__comments.json
+tests/integration/cassettes/TestRedditor.test_stream__submissions.json
tests/integration/cassettes/TestRedditor.test_unblock.json
tests/integration/cassettes/TestRedditor.test_unfriend.json
tests/integration/cassettes/TestRedditorListings.test_comments__controversial.json
@@ -348,6 +359,9 @@ tests/integration/cassettes/TestRedditorListings.test_upvoted__in_read_only_mode
tests/integration/cassettes/TestRedditorListings.test_upvoted__other_user.json
tests/integration/cassettes/TestSubmission.test_clear_vote.json
tests/integration/cassettes/TestSubmission.test_comments.json
+tests/integration/cassettes/TestSubmission.test_crosspost.json
+tests/integration/cassettes/TestSubmission.test_crosspost__custom_title.json
+tests/integration/cassettes/TestSubmission.test_crosspost__subreddit_object.json
tests/integration/cassettes/TestSubmission.test_delete.json
tests/integration/cassettes/TestSubmission.test_disable_inbox_replies.json
tests/integration/cassettes/TestSubmission.test_downvote.json
@@ -355,6 +369,7 @@ tests/integration/cassettes/TestSubmission.test_duplicates.json
tests/integration/cassettes/TestSubmission.test_edit.json
tests/integration/cassettes/TestSubmission.test_enable_inbox_replies.json
tests/integration/cassettes/TestSubmission.test_gild__no_creddits.json
+tests/integration/cassettes/TestSubmission.test_gilded.json
tests/integration/cassettes/TestSubmission.test_hide.json
tests/integration/cassettes/TestSubmission.test_hide__multiple.json
tests/integration/cassettes/TestSubmission.test_hide__multiple_in_batches.json
@@ -507,6 +522,7 @@ tests/integration/cassettes/TestSubredditStylesheet.test_update.json
tests/integration/cassettes/TestSubredditStylesheet.test_update__with_reason.json
tests/integration/cassettes/TestSubredditStylesheet.test_upload.json
tests/integration/cassettes/TestSubredditStylesheet.test_upload__invalid.json
+tests/integration/cassettes/TestSubredditStylesheet.test_upload__invalid_ext.json
tests/integration/cassettes/TestSubredditStylesheet.test_upload__others_invalid.json
tests/integration/cassettes/TestSubredditStylesheet.test_upload__others_too_large.json
tests/integration/cassettes/TestSubredditStylesheet.test_upload__too_large.json
diff --git a/praw.egg-info/requires.txt b/praw.egg-info/requires.txt
index dc47b5b..d28f27c 100644
--- a/praw.egg-info/requires.txt
+++ b/praw.egg-info/requires.txt
@@ -1,2 +1,2 @@
-prawcore >=0.11.0, <0.12
-update_checker >=0.16
+prawcore<0.13,>=0.12.0
+update_checker>=0.16
diff --git a/praw/const.py b/praw/const.py
index 554822b..e916910 100644
--- a/praw/const.py
+++ b/praw/const.py
@@ -2,7 +2,7 @@
import sys
-__version__ = '5.0.1'
+__version__ = '5.2.0'
API_PATH = {
'about_edited': 'r/{subreddit}/about/edited/',
@@ -18,6 +18,7 @@ API_PATH = {
'approve': 'api/approve/',
'block': 'api/block',
'blocked': 'prefs/blocked/',
+ 'collapse': 'api/collapse_message/',
'comment': 'api/comment/',
'comment_replies': 'message/comments/',
'compose': 'api/compose/',
@@ -64,6 +65,7 @@ API_PATH = {
'live_close': 'api/live/{id}/close_thread',
'live_contributors': 'live/{id}/contributors',
'live_discussions': 'live/{id}/discussions',
+ 'live_focus': 'live/{thread_id}/updates/{update_id}',
'live_info': 'api/live/by_id/{ids}',
'live_invite': 'api/live/{id}/invite_contributor',
'live_leave': 'api/live/{id}/leave_contributor',
@@ -147,6 +149,7 @@ API_PATH = {
'subreddits_search': 'subreddits/search/',
'subscribe': 'api/subscribe/',
'suggested_sort': 'api/set_suggested_sort/',
+ 'uncollapse': 'api/uncollapse_message/',
'unfriend': 'r/{subreddit}/api/unfriend/',
'unhide': 'api/unhide/',
'unignore_reports': 'api/unignore_reports/',
diff --git a/praw/exceptions.py b/praw/exceptions.py
index 8704a88..7bc45cd 100644
--- a/praw/exceptions.py
+++ b/praw/exceptions.py
@@ -21,10 +21,18 @@ class APIException(PRAWException):
:param message: The associated message for the error.
:param field: The input field associated with the error if available.
+ .. note:: Calling ``str()`` on the instance returns
+ ``unicode_escape``-d ASCII string because the message may be
+ localized and may contain UNICODE characters. If you want a
+ non-escaped message, access the ``message`` attribute on
+ the instance.
+
"""
- error_str = '{}: \'{}\''.format(error_type, message)
+ error_str = u'{}: \'{}\''.format(error_type, message)
if field:
- error_str += ' on field \'{}\''.format(field)
+ error_str += u' on field \'{}\''.format(field)
+ error_str = error_str.encode('unicode_escape').decode('ascii')
+
super(APIException, self).__init__(error_str)
self.error_type = error_type
self.message = message
diff --git a/praw/models/comment_forest.py b/praw/models/comment_forest.py
index 7152e73..b3bbb24 100644
--- a/praw/models/comment_forest.py
+++ b/praw/models/comment_forest.py
@@ -16,7 +16,7 @@ class CommentForest(object):
"""Return a list of MoreComments objects obtained from tree."""
more_comments = []
queue = [(None, x) for x in tree]
- while len(queue) > 0:
+ while queue:
parent, comment = queue.pop(0)
if isinstance(comment, MoreComments):
heappush(more_comments, comment)
diff --git a/praw/models/inbox.py b/praw/models/inbox.py
index e1ea61d..163c3ac 100644
--- a/praw/models/inbox.py
+++ b/praw/models/inbox.py
@@ -25,6 +25,34 @@ class Inbox(PRAWBase):
return ListingGenerator(self._reddit, API_PATH['inbox'],
**generator_kwargs)
+ def collapse(self, items):
+ """Mark an inbox message as collapsed.
+
+ :param items: A list containing instances of :class:`.Message`.
+
+ Requests are batched at 25 items (reddit limit).
+
+ For example, to collapse all unread Messages, try:
+
+ .. code:: python
+
+ from praw.models import Message
+ unread_messages = []
+ for item in reddit.inbox.unread(limit=None):
+ if isinstance(item, Message):
+ unread_messages.append(item)
+ reddit.inbox.collapse(unread_messages)
+
+ .. seealso::
+
+ :meth:`.Message.uncollapse`
+
+ """
+ while items:
+ data = {'id': ','.join(x.fullname for x in items[:25])}
+ self._reddit.post(API_PATH['collapse'], data=data)
+ items = items[25:]
+
def comment_replies(self, **generator_kwargs):
"""Return a ListingGenerator for comment replies.
@@ -208,6 +236,34 @@ class Inbox(PRAWBase):
return ListingGenerator(self._reddit, API_PATH['submission_replies'],
**generator_kwargs)
+ def uncollapse(self, items):
+ """Mark an inbox message as uncollapsed.
+
+ :param items: A list containing instances of :class:`.Message`.
+
+ Requests are batched at 25 items (reddit limit).
+
+ For example, to uncollapse all unread Messages, try:
+
+ .. code:: python
+
+ from praw.models import Message
+ unread_messages = []
+ for item in reddit.inbox.unread(limit=None):
+ if isinstance(item, Message):
+ unread_messages.append(item)
+ reddit.inbox.uncollapse(unread_messages)
+
+ .. seealso::
+
+ :meth:`.Message.collapse`
+
+ """
+ while items:
+ data = {'id': ','.join(x.fullname for x in items[:25])}
+ self._reddit.post(API_PATH['uncollapse'], data=data)
+ items = items[25:]
+
def unread(self, mark_read=False, **generator_kwargs):
"""Return a ListingGenerator for unread comments and messages.
diff --git a/praw/models/listing/generator.py b/praw/models/listing/generator.py
index 5d6a492..ae6765e 100644
--- a/praw/models/listing/generator.py
+++ b/praw/models/listing/generator.py
@@ -66,7 +66,7 @@ class ListingGenerator(PRAWBase):
self._listing = FlairListing(self._reddit, self._listing)
self._list_index = 0
- if len(self._listing) == 0:
+ if not self._listing:
raise StopIteration()
if self._listing.after:
diff --git a/praw/models/listing/mixins/submission.py b/praw/models/listing/mixins/submission.py
index 5cc50ba..83a00cb 100644
--- a/praw/models/listing/mixins/submission.py
+++ b/praw/models/listing/mixins/submission.py
@@ -1,10 +1,10 @@
"""Provide the SubmissionListingMixin class."""
from ....const import API_PATH
+from ...base import PRAWBase
from ..generator import ListingGenerator
-from .gilded import GildedListingMixin
-class SubmissionListingMixin(GildedListingMixin):
+class SubmissionListingMixin(PRAWBase):
"""Adds additional methods pertaining to Submission instances."""
def duplicates(self, **generator_kwargs):
diff --git a/praw/models/listing/mixins/subreddit.py b/praw/models/listing/mixins/subreddit.py
index ad921e0..40c957e 100644
--- a/praw/models/listing/mixins/subreddit.py
+++ b/praw/models/listing/mixins/subreddit.py
@@ -1,5 +1,6 @@
"""Provide the SubredditListingMixin class."""
from ....const import urljoin
+from ...base import PRAWBase
from ..generator import ListingGenerator
from .base import BaseListingMixin
from .gilded import GildedListingMixin
@@ -37,7 +38,7 @@ class SubredditListingMixin(BaseListingMixin, GildedListingMixin,
self._comments = None
-class CommentHelper(GildedListingMixin):
+class CommentHelper(PRAWBase):
"""Provide a set of functions to interact with a subreddit's comments."""
@property
@@ -64,3 +65,17 @@ class CommentHelper(GildedListingMixin):
"""
return ListingGenerator(self._reddit, self._path, **generator_kwargs)
+
+ def gilded(self, **generator_kwargs):
+ """Deprecated.
+
+ .. warning:: (Deprecated) This method will be removed in PRAW 6 because
+ it doesn't actually restrict the results to gilded
+ Comments. Use ``subreddit.gilded`` instead.
+
+ Additional keyword arguments are passed in the initialization of
+ :class:`.ListingGenerator`.
+
+ """
+ return ListingGenerator(self._reddit, urljoin(self._path, 'gilded'),
+ **generator_kwargs)
diff --git a/praw/models/reddit/comment.py b/praw/models/reddit/comment.py
index 62c32a5..05816a5 100644
--- a/praw/models/reddit/comment.py
+++ b/praw/models/reddit/comment.py
@@ -1,5 +1,4 @@
"""Provide the Comment class."""
-from ...const import urljoin
from ...exceptions import ClientException
from ..comment_forest import CommentForest
from .base import RedditBase
@@ -10,6 +9,8 @@ from .redditor import Redditor
class Comment(RedditBase, InboxableMixin, UserContentMixin):
"""A class that represents a reddit comments."""
+ MISSING_COMMENT_MESSAGE = ('This comment does not appear to be in the '
+ 'comment tree')
STR_FIELD = 'id'
@property
@@ -43,9 +44,9 @@ class Comment(RedditBase, InboxableMixin, UserContentMixin):
@submission.setter
def submission(self, submission):
"""Update the Submission associated with the Comment."""
- assert self.name not in submission._comments_by_id
submission._comments_by_id[self.name] = self
self._submission = submission
+ # pylint: disable=not-an-iterable
for reply in getattr(self, 'replies', []):
reply.submission = submission
@@ -63,7 +64,6 @@ class Comment(RedditBase, InboxableMixin, UserContentMixin):
def __setattr__(self, attribute, value):
"""Objectify author, replies, and subreddit."""
- # pylint: disable=redefined-variable-type
if attribute == 'author':
value = Redditor.from_data(self._reddit, value)
elif attribute == 'replies':
@@ -79,8 +79,7 @@ class Comment(RedditBase, InboxableMixin, UserContentMixin):
def _extract_submission_id(self):
if 'context' in self.__dict__:
return self.context.rsplit('/', 4)[1]
- else:
- return self.link_id.split('_', 1)[1]
+ return self.link_id.split('_', 1)[1]
def parent(self):
"""Return the parent of the comment.
@@ -106,13 +105,38 @@ class Comment(RedditBase, InboxableMixin, UserContentMixin):
parent.refresh()
print(parent.replies) # Output is at least: [Comment(id='cklhv0f')]
+ .. warning:: Successive calls to :meth:`.parent()` may result in a
+ network request per call when the comment is not obtained through a
+ :class:`.Submission`. See below for an example of how to minimize
+ requests.
+
+ If you have a deeply nested comment and wish to most efficiently
+ discover its top-most :class:`.Comment` ancestor you can chain
+ successive calls to :meth:`.parent()` with calls to :meth:`.refresh()`
+ at every 9 levels. For example:
+
+ .. code:: python
+
+ comment = reddit.comment('dkk4qjd')
+ ancestor = comment
+ refresh_counter = 0
+ while not ancestor.is_root:
+ ancestor = ancestor.parent()
+ if refresh_counter % 9 == 0:
+ ancestor.refresh()
+ refresh_counter += 1
+ print('Top-most Ancestor: {}'.format(ancestor))
+
+ The above code should result in 5 network requests to Reddit. Without
+ the calls to :meth:`.refresh` it would make at least 31 network
+ requests.
+
"""
# pylint: disable=no-member
if self.parent_id == self.submission.fullname:
return self.submission
- if '_comments' in self.submission.__dict__ \
- and self.parent_id in self.submission._comments_by_id:
+ if self.parent_id in self.submission._comments_by_id:
# The Comment already exists, so simply return it
return self.submission._comments_by_id[self.parent_id]
# pylint: enable=no-member
@@ -121,27 +145,6 @@ class Comment(RedditBase, InboxableMixin, UserContentMixin):
parent._submission = self.submission
return parent
- def permalink(self, fast=False):
- """Return a permalink to the comment.
-
- :param fast: Return the result as quickly as possible (default: False).
-
- In order to determine the full permalink for a comment, the Submission
- may need to be fetched if it hasn't been already. Set ``fast=True`` if
- you want to bypass that possible load.
-
- A full permalink looks like:
- /r/redditdev/comments/2gmzqe/praw_https_enabled/cklhv0f
-
- A fast-loaded permalink for the same comment will look like:
- /comments/2gmzqe//cklhv0f
-
- """
- # pylint: disable=no-member
- if not fast or 'permalink' in self.submission.__dict__:
- return urljoin(self.submission.permalink, self.id)
- return '/comments/{}//{}'.format(self.submission.id, self.id)
-
def refresh(self):
"""Refresh the comment's attributes.
@@ -155,16 +158,29 @@ class Comment(RedditBase, InboxableMixin, UserContentMixin):
comment_path = '{}_/{}'.format(
self.submission._info_path(), # pylint: disable=no-member
self.id)
- comment_list = self._reddit.get(comment_path)[1].children
+
+ # The context limit appears to be 8, but let's ask for more anyway.
+ comment_list = self._reddit.get(comment_path,
+ params={'context': 100})[1].children
if not comment_list:
- raise ClientException('Comment has been deleted')
- comment = comment_list[0]
+ raise ClientException(self.MISSING_COMMENT_MESSAGE)
+
+ # With context, the comment may be nested so we have to find it
+ comment = None
+ queue = comment_list[:]
+ while queue and (comment is None or comment.id != self.id):
+ comment = queue.pop()
+ if isinstance(comment, Comment):
+ queue.extend(comment._replies)
+
+ if comment.id != self.id:
+ raise ClientException(self.MISSING_COMMENT_MESSAGE)
if self._submission is not None:
del comment.__dict__['_submission'] # Don't replace if set
self.__dict__.update(comment.__dict__)
- for reply in comment._replies:
+ for reply in comment_list:
reply.submission = self.submission
return self
diff --git a/praw/models/reddit/live.py b/praw/models/reddit/live.py
index 4583baf..8cdb5be 100644
--- a/praw/models/reddit/live.py
+++ b/praw/models/reddit/live.py
@@ -289,9 +289,6 @@ class LiveThread(RedditBase):
def __getitem__(self, update_id):
"""Return a lazy :class:`.LiveUpdate` instance.
- .. warning:: At this time, accessing lazy attributes, whose value
- have not loaded, raises ``AttributeError``.
-
:param update_id: A live update ID, e.g.,
``'7827987a-c998-11e4-a0b9-22000b6a88d2'``.
@@ -303,7 +300,7 @@ class LiveThread(RedditBase):
update = thread['7827987a-c998-11e4-a0b9-22000b6a88d2']
update.thread # LiveThread(id='ukaeu1ik4sw5')
update.id # '7827987a-c998-11e4-a0b9-22000b6a88d2'
- update.author # raise ``AttributeError``
+ update.author # 'umbrae'
"""
return LiveUpdate(self._reddit, self.id, update_id)
@@ -495,31 +492,7 @@ class LiveThreadContribution(object):
class LiveUpdate(RedditBase):
- """An individual :class:`.LiveUpdate` object.
-
- .. warning:: At this time, accessing lazy attributes on this class
- may raises ``AttributeError``: if an update is instantiated
- through :meth:`.LiveThread.updates`, the exception is not
- thrown. For example:
-
- .. code-block:: python
-
- thread = reddit.live('xyu8kmjvfrww')
- for update in thread.updates(limit=None):
- if update.id == 'cb5fe532-dbee-11e6-9a91-0e6d74fabcc4':
- print(update.stricken) # True
- break
-
- But the update is instantiated through ``thread[update_id]``
- or ``LiveUpdate(reddit, update_id)``, ``AttributeError`` is thrown:
-
- .. code-block:: python
-
- thread = reddit.live('xyu8kmjvfrww')
- update = thread['cb5fe532-dbee-11e6-9a91-0e6d74fabcc4']
- update.stricken # raise AttributeError
-
- """
+ """An individual :class:`.LiveUpdate` object."""
STR_FIELD = 'id'
@@ -551,10 +524,6 @@ class LiveUpdate(RedditBase):
Either ``thread_id`` and ``update_id``, or ``_data`` must be
provided.
- .. warning:: At this time, accessing lazy attributes, whose value
- have not loaded, raises ``AttributeError``. See :class:`.LiveUpdate`
- for details.
-
:param reddit: An instance of :class:`.Reddit`.
:param thread_id: A live thread ID, e.g., ``'ukaeu1ik4sw5'``.
:param update_id: A live update ID, e.g.,
@@ -568,13 +537,14 @@ class LiveUpdate(RedditBase):
'7827987a-c998-11e4-a0b9-22000b6a88d2')
update.thread # LiveThread(id='ukaeu1ik4sw5')
update.id # '7827987a-c998-11e4-a0b9-22000b6a88d2'
- update.author # raise ``AttributeError``
+ update.author # 'umbrae'
"""
if _data is not None:
# Since _data (part of JSON returned from reddit) have no
# thread ID, self._thread must be set by the caller of
# LiveUpdate(). See the code of LiveThread.updates() for example.
super(LiveUpdate, self).__init__(reddit, _data)
+ self._fetched = True
elif thread_id and update_id:
super(LiveUpdate, self).__init__(reddit, None)
self._thread = LiveThread(self._reddit, thread_id)
@@ -582,7 +552,6 @@ class LiveUpdate(RedditBase):
else:
raise TypeError('Either `thread_id` and `update_id`, or '
'`_data` must be provided.')
- self._fetched = True
self._contrib = None
def __setattr__(self, attribute, value):
@@ -591,6 +560,13 @@ class LiveUpdate(RedditBase):
value = Redditor(self._reddit, name=value)
super(LiveUpdate, self).__setattr__(attribute, value)
+ def _fetch(self):
+ url = API_PATH['live_focus'].format(
+ thread_id=self.thread.id, update_id=self.id)
+ other = self._reddit.get(url)[0]
+ self.__dict__.update(other.__dict__)
+ self._fetched = True
+
class LiveUpdateContribution(object):
"""Provides a set of contribution functions to LiveUpdate."""
diff --git a/praw/models/reddit/mixins/inboxable.py b/praw/models/reddit/mixins/inboxable.py
index b3096eb..368f784 100644
--- a/praw/models/reddit/mixins/inboxable.py
+++ b/praw/models/reddit/mixins/inboxable.py
@@ -16,6 +16,15 @@ class InboxableMixin(object):
"""
self._reddit.post(API_PATH['block'], data={'id': self.fullname})
+ def collapse(self):
+ """Mark the item as collapsed.
+
+ .. note:: This method pertains only to objects which were retrieved via
+ the inbox.
+
+ """
+ self._reddit.inbox.collapse([self])
+
def mark_read(self):
"""Mark the item as read.
@@ -33,3 +42,12 @@ class InboxableMixin(object):
"""
self._reddit.inbox.mark_unread([self])
+
+ def uncollapse(self):
+ """Mark the item as uncollapsed.
+
+ .. note:: This method pertains only to objects which were retrieved via
+ the inbox.
+
+ """
+ self._reddit.inbox.uncollapse([self])
diff --git a/praw/models/reddit/more.py b/praw/models/reddit/more.py
index 30ecd9b..7a2252b 100644
--- a/praw/models/reddit/more.py
+++ b/praw/models/reddit/more.py
@@ -35,7 +35,7 @@ class MoreComments(PRAWBase):
self.__class__.__name__, self.count, children)
def _continue_comments(self, update):
- assert len(self.children) == 0
+ assert not self.children
parent = self._load_comment(self.parent_id.split('_', 1)[1])
self._comments = parent.replies
if update:
diff --git a/praw/models/reddit/redditor.py b/praw/models/reddit/redditor.py
index 15e901d..908d613 100644
--- a/praw/models/reddit/redditor.py
+++ b/praw/models/reddit/redditor.py
@@ -3,6 +3,7 @@ from json import dumps
from ...const import API_PATH
from ..listing.mixins import RedditorListingMixin
+from ..util import stream_generator
from .base import RedditBase
from .mixins import MessageableMixin
@@ -17,8 +18,33 @@ class Redditor(RedditBase, MessageableMixin, RedditorListingMixin):
"""Return an instance of Redditor, or None from ``data``."""
if data == '[deleted]':
return None
- else:
- return cls(reddit, data)
+ return cls(reddit, data)
+
+ @property
+ def stream(self):
+ """Provide an instance of :class:`.RedditorStream`.
+
+ Streams can be used to indefinitely retrieve new comments made by a
+ redditor, like:
+
+ .. code:: python
+
+ for comment in reddit.redditor('spez').stream.comments():
+ print(comment)
+
+ Additionally, new submissions can be retrieved via the stream. In the
+ following example all submissions are fetched via the redditor
+ ``spez``:
+
+ .. code:: python
+
+ for submission in reddit.redditor('spez').stream.submissions():
+ print(submission)
+
+ """
+ if self._stream is None:
+ self._stream = RedditorStream(self)
+ return self._stream
def __init__(self, reddit, name=None, _data=None):
"""Initialize a Redditor instance.
@@ -34,6 +60,7 @@ class Redditor(RedditBase, MessageableMixin, RedditorListingMixin):
if name:
self.name = name
self._path = API_PATH['user'].format(user=self)
+ self._stream = None
def _info_path(self):
return API_PATH['user_about'].format(user=self)
@@ -93,3 +120,54 @@ class Redditor(RedditBase, MessageableMixin, RedditorListingMixin):
def unfriend(self):
"""Unfriend the Redditor."""
self._friend(method='DELETE', data={'id': str(self)})
+
+
+class RedditorStream(object):
+ """Provides submission and comment streams."""
+
+ def __init__(self, redditor):
+ """Create a RedditorStream instance.
+
+ :param redditor: The redditor associated with the streams.
+
+ """
+ self.redditor = redditor
+
+ def comments(self, **stream_options):
+ """Yield new comments as they become available.
+
+ Comments are yielded oldest first. Up to 100 historical comments will
+ initially be returned.
+
+ Keyword arguments are passed to :meth:`.stream_generator`.
+
+ For example, to retrieve all new comments made by redditor ``spez``,
+ try:
+
+ .. code:: python
+
+ for comment in reddit.redditor('spez').stream.comments():
+ print(comment)
+
+ """
+ return stream_generator(self.redditor.comments.new, **stream_options)
+
+ def submissions(self, **stream_options):
+ """Yield new submissions as they become available.
+
+ Submissions are yielded oldest first. Up to 100 historical submissions
+ will initially be returned.
+
+ Keyword arguments are passed to :meth:`.stream_generator`.
+
+ For example to retrieve all new submissions made by redditor
+ ``spez``, try:
+
+ .. code:: python
+
+ for submission in reddit.redditor('spez').stream.submissions():
+ print(submission)
+
+ """
+ return stream_generator(self.redditor.submissions.new,
+ **stream_options)
diff --git a/praw/models/reddit/submission.py b/praw/models/reddit/submission.py
index b8b77aa..96cc82e 100644
--- a/praw/models/reddit/submission.py
+++ b/praw/models/reddit/submission.py
@@ -129,7 +129,6 @@ class Submission(RedditBase, SubmissionListingMixin, UserContentMixin):
def __setattr__(self, attribute, value):
"""Objectify author, and subreddit attributes."""
- # pylint: disable=redefined-variable-type
if attribute == 'author':
value = Redditor.from_data(self._reddit, value)
elif attribute == 'subreddit':
@@ -191,6 +190,29 @@ class Submission(RedditBase, SubmissionListingMixin, UserContentMixin):
for submissions in self._chunk(other_submissions, 50):
self._reddit.post(API_PATH['unhide'], data={'id': submissions})
+ def crosspost(self, subreddit, title=None, send_replies=True):
+ """Crosspost the submission to a subreddit.
+
+ :param subreddit: Name of the subreddit or :class:`~.Subreddit`
+ object to crosspost into.
+ :param title: Title of the submission. Will use this submission's
+ title if `None` (default: None).
+ :param send_replies: When True, messages will be sent to the
+ submission author when comments are made to the submission
+ (default: True).
+ :returns: A :class:`~.Submission` object for the newly created
+ submission.
+ """
+ if title is None:
+ title = self.title
+
+ data = {'sr': str(subreddit),
+ 'title': title,
+ 'sendreplies': bool(send_replies),
+ 'kind': 'crosspost',
+ 'crosspost_fullname': self.fullname}
+ return self._reddit.post(API_PATH['submit'], data=data)
+
class SubmissionFlair(object):
"""Provide a set of functions pertaining to Submission flair."""
diff --git a/praw/models/reddit/subreddit.py b/praw/models/reddit/subreddit.py
index f35f0c5..c96b441 100644
--- a/praw/models/reddit/subreddit.py
+++ b/praw/models/reddit/subreddit.py
@@ -41,7 +41,11 @@ class Subreddit(RedditBase, MessageableMixin, SubredditListingMixin):
for submission in reddit.subreddit('redditdev+learnpython').top('all'):
print(submission)
- Subreddits can be filtered from combined listings as follows:
+ Subreddits can be filtered from combined listings as follows. Note that
+ these filters are ignored by certain methods, including
+ :attr:`~praw.models.Subreddit.comments`,
+ :meth:`~praw.models.Subreddit.gilded`, and
+ :meth:`.SubredditStream.comments`.
.. code:: python
@@ -141,7 +145,17 @@ class Subreddit(RedditBase, MessageableMixin, SubredditListingMixin):
@property
def contributor(self):
- """Provide an instance of :class:`.ContributorRelationship`."""
+ """Provide an instance of :class:`.ContributorRelationship`.
+
+ Contributors are also known as approved submitters.
+
+ To add a contributor try:
+
+ .. code-block:: python
+
+ reddit.subreddit('SUBREDDIT').contributor.add('NAME')
+
+ """
if self._contributor is None:
self._contributor = ContributorRelationship(self, 'contributor')
return self._contributor
@@ -781,7 +795,7 @@ class SubredditFlair(object):
response = []
url = API_PATH['flaircsv'].format(subreddit=self.subreddit)
- while len(lines):
+ while lines:
data = {'flair_csv': '\n'.join(lines[:100])}
response.extend(self.subreddit._reddit.post(url, data=data))
lines = lines[100:]
... 7707 lines suppressed ...
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/praw.git
More information about the Python-modules-commits
mailing list