[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