[Python-modules-commits] [praw] 01/01: Imported Upstream version 3.3.0

Riley Baird orthogonal-guest at moszumanska.debian.org
Sun Nov 29 09:36:39 UTC 2015


This is an automated email from the git hooks/post-receive script.

orthogonal-guest pushed a commit to branch upstream
in repository praw.

commit dba9360b0bbe689b863911f2defc7e82bacf9c9d
Author: Riley Baird <BM-2cVqnDuYbAU5do2DfJTrN7ZbAJ246S4XiX at bitmessage.ch>
Date:   Sat Nov 28 16:43:34 2015 +1100

    Imported Upstream version 3.3.0
---
 CHANGES.rst                                        |  36 +++++++
 PKG-INFO                                           |   9 +-
 README.rst                                         |   2 +-
 docs/conf.py                                       |   6 +-
 docs/pages/comment_parsing.rst                     |  14 +++
 docs/pages/configuration_files.rst                 |  32 ++++++-
 docs/pages/contributor_guidelines.rst              |   2 +-
 docs/pages/faq.rst                                 |   8 +-
 docs/pages/getting_started.rst                     |   7 +-
 docs/pages/oauth.rst                               |   6 ++
 docs/pages/useful_scripts.rst                      |  18 +++-
 praw.egg-info/PKG-INFO                             |   9 +-
 praw.egg-info/SOURCES.txt                          |   6 ++
 praw/__init__.py                                   | 105 +++++++++++++++------
 praw/errors.py                                     |  50 +++++++---
 praw/helpers.py                                    |  14 +--
 praw/internal.py                                   |  13 ++-
 praw/objects.py                                    |  86 ++++++++++++++---
 praw/praw.ini                                      |   1 +
 setup.py                                           |   5 +-
 tests/cassettes/test_friends_oauth.json            |   1 +
 tests/cassettes/test_get_edited_oauth.json         |   1 +
 tests/cassettes/test_hide_oauth.json               |   1 +
 .../test_set_access_credentials_with_list.json     |   1 +
 .../test_set_access_credentials_with_string.json   |   1 +
 tests/helper.py                                    |   9 +-
 tests/test_comments.py                             |   2 +
 tests/test_config.py                               |  42 +++++++++
 tests/test_oauth2_reddit.py                        |  65 +++++++++++++
 tests/test_redditor.py                             |  34 ++++++-
 tests/test_submission.py                           |  93 ++++++++++--------
 tests/test_subreddit.py                            |  13 ++-
 32 files changed, 544 insertions(+), 148 deletions(-)

diff --git a/CHANGES.rst b/CHANGES.rst
index c7f1563..2ba7ed3 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -22,6 +22,42 @@ formatted links that link to the relevant place in the code overview.
 
 .. begin_changelog_body
 
+PRAW 3.3.0
+----------
+ * **[BUGFIX]** Fixed login password prompt issue on windows (#485).
+ * **[BUGFIX]** Fixed unicode user-agent issue (#483).
+ * **[BUGFIX]** Fix duplicate request issue with comment and submission streams
+   (#501).
+ * **[BUGFIX]** Stopped :meth:`praw.objects.Redditor.friend` from raising
+   LoginRequired when using OAuth.
+ * **[BUGFIX]** Stopped a json-parsing error from occuring in cases where
+   reddit's response to a request was an empty string. :meth:`request_json`
+   will now simply return that empty string.
+ * **[BUGFIX]** Fix AssertionError when hiding and unhiding under OAuth, raised
+   by stacked scope decorators.
+ * **[BUGFIX]** Fix AttributeError when hiding and unhiding under OAuth without
+   the "identity" scope, raised when PRAW tried to evict the user's /hidden
+   page from the cache.
+ * **[CHANGE]** Added messages to all PRAW exceptions (#491).
+ * **[CHANGE]** Made it easier to send JSON dumps instead of form-encoded data
+   for http requests. Some api-v1 endpoints require the request body to be in
+   the json format.
+ * **[CHANGE]** Moved and deprecated
+   :meth:`praw.objects.LoggedInRedditor.get_friends` to
+   :class:`praw.AuthenticatedReddit`, leaving a pointer in its place.
+   Previously, `get_friends` was difficult to access because the only instance
+   of `LoggedInRedditor` was the reddit session's `user` attribute, which is
+   only instantiated if the user has the "identity" scope. By moving
+   `get_friends` to the reddit session, it can be used without having to
+   manipulate a :class:`praw.objects.Redditor` intsance's class.
+ * **[CHANGE]** Removed support for Python 2.6 and Python 3.2 (#532).
+ * **[FEATURE]** Added support for adding "notes" to your friends. Users with
+   reddit Gold can set the ``note`` parameter of 
+   :meth:`praw.objects.Redditor.friend`. 300 character max enforced by reddit.
+ * **[FEATURE]** New :meth:`praw.objects.Redditor.get_friend_info` to see info
+   about one of your friends. Includes their name, ID, when you added them, and
+   if you have reddit Gold, your note about them.
+
 PRAW 3.2.1
 ----------
  * **[BUGFIX]** Fixed "multiple values for argument" error when solving
diff --git a/PKG-INFO b/PKG-INFO
index 8a51383..0510d87 100644
--- a/PKG-INFO
+++ b/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: praw
-Version: 3.2.1
+Version: 3.3.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
@@ -55,7 +55,7 @@ Description: .. _main_page:
         Installation
         ------------
         
-        PRAW works with python 2.6, 2.7, 3.1, 3.2, 3.3, and 3.4. The recommended way to
+        PRAW is supported on python 2.7, 3.3, 3.4 and 3.5. The recommended way to
         install is via `pip <https://pypi.python.org/pypi/pip>`_
         
         .. code-block:: bash
@@ -132,11 +132,8 @@ Classifier: Intended Audience :: Developers
 Classifier: License :: OSI Approved :: GNU General Public License (GPL)
 Classifier: Natural Language :: English
 Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python :: 2.6
 Classifier: Programming Language :: Python :: 2.7
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.1
-Classifier: Programming Language :: Python :: 3.2
 Classifier: Programming Language :: Python :: 3.3
 Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
 Classifier: Topic :: Utilities
diff --git a/README.rst b/README.rst
index 69b06b4..a002fb6 100644
--- a/README.rst
+++ b/README.rst
@@ -47,7 +47,7 @@ This will display something similar to the following:
 Installation
 ------------
 
-PRAW works with python 2.6, 2.7, 3.1, 3.2, 3.3, and 3.4. The recommended way to
+PRAW is supported on python 2.7, 3.3, 3.4 and 3.5. The recommended way to
 install is via `pip <https://pypi.python.org/pypi/pip>`_
 
 .. code-block:: bash
diff --git a/docs/conf.py b/docs/conf.py
index d0ce8a4..c810492 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -235,10 +235,10 @@ man_pages = [
 # (source start file, target name, title, author,
 #  dir menu entry, description, category)
 texinfo_documents = [
-    ('index', 'PythonRedditAPIWrapper',
+    ('index', 'praw',
      u'Python Reddit API Wrapper Documentation',
-     u'Bryce Boe', 'PythonRedditAPIWrapper',
-     'One line description of project.', 'Miscellaneous'),
+     u'Bryce Boe', 'praw',
+     'Python Reddit API Wrapper', 'Python'),
 ]
 
 # Documents to append as an appendix to all manuals.
diff --git a/docs/pages/comment_parsing.rst b/docs/pages/comment_parsing.rst
index bb50c79..17dfa4d 100644
--- a/docs/pages/comment_parsing.rst
+++ b/docs/pages/comment_parsing.rst
@@ -126,3 +126,17 @@ The full program
         if comment.body == "Hello" and comment.id not in already_done:
             comment.reply(' world!')
             already_done.add(comment.id)
+
+[deleted] comments
+------------------
+
+When a comment is deleted, in most cases, that comment will not be viewable with a
+browser nor the API. However, if a comment is made, and then a reply to that comment
+is made, and *then* the original comment is deleted, that comment will have its
+``body`` and ``author`` attributes be ``NoneType`` via the API. The same goes with
+removed comments, unless the authenticated account is a mod of the subreddit whose
+comments you are getting. If you are a mod, and said comments are removed comments,
+they are left intact.
+
+If a comment is made and then the account that left that comment is deleted, the
+comment body is left intact, while the ``author`` attribute becomes ``NoneType``.
\ No newline at end of file
diff --git a/docs/pages/configuration_files.rst b/docs/pages/configuration_files.rst
index 0d011e9..8164f2f 100644
--- a/docs/pages/configuration_files.rst
+++ b/docs/pages/configuration_files.rst
@@ -71,9 +71,9 @@ config file. Each site can overwrite any of these variables.
   will pick up the environment variable for https_proxy, if it has been set.
 * *log_requests:* An **integer** that determines the level of API call logging.
 
- * **0:** no logging
- * **1:** log only the request URIs
- * **2:** log the request URIs as well as any POST data
+  * **0:** no logging
+  * **1:** log only the request URIs
+  * **2:** log the request URIs as well as any POST data
 
 * *oauth_domain:* A **string** that defines the *domain* where OAuth
   authenticated requests are sent.
@@ -155,7 +155,31 @@ The default provided sites are:
   'local' site in your *user*-level or *local*-level ``praw.ini`` file.
 
 Additional sites can be added to represent other instances of reddit or simply
-provide an additional set of credentials for easy access to that account.
+provide an additional set of credentials for easy access to that account. This
+is done by adding ``[YOUR_SITE]`` to the ``praw.ini`` file and then calling it
+in :class:`praw.Reddit`. For example, you could add the following to your
+``praw.ini`` file:
+
+.. code-block:: text
+
+    [YOUR_SITE]
+    domain: www.myredditsite.com
+    ssl_domain: ssl.myredditsite.com
+    user: bboe
+    pswd: this_isn't_my_password
+    api_request_delay: 7.0
+
+From there, to specify the reddit instance of "YOUR_SITE", you would do something
+like this:
+
+.. code-block:: python
+
+    import praw
+
+    r = praw.Reddit(user_agent='Custom Site Example for PRAW',
+	                site_name='YOUR_SITE')
+
+Of course, you can use any of the above Configuration Variables as well.
 
 Example praw.ini file
 ^^^^^^^^^^^^^^^^^^^^^
diff --git a/docs/pages/contributor_guidelines.rst b/docs/pages/contributor_guidelines.rst
index be45f5b..251697d 100644
--- a/docs/pages/contributor_guidelines.rst
+++ b/docs/pages/contributor_guidelines.rst
@@ -33,7 +33,7 @@ Documentation
 
 * All publicly available functions, classes and modules should have a
   docstring.
-* Use correct terminology. A subreddits name is something like ' t3_xyfc7'.
+* Use correct terminology. A subreddits name is something like ' t5_xyfc7'.
   The correct term for a subreddits "name" like
   `python <http://www.reddit.com/r/python>`_ is its display name.
 * When referring to any reddit. Refer to it as 'reddit'. When you are speaking
diff --git a/docs/pages/faq.rst b/docs/pages/faq.rst
index de791ba..64cbe65 100644
--- a/docs/pages/faq.rst
+++ b/docs/pages/faq.rst
@@ -71,7 +71,10 @@ Some commands take a while. Why?
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
 PRAW follows the `api guidelines <https://github.com/reddit/reddit/wiki/API>`_
-which require a 2 second delay between each API call.
+which require a 2 second delay between each API call via CookieAuth. If you
+are exclusively using OAuth2, you are allowed to change this delay in your
+``praw.ini`` file to be a 1 second delay. This will be the default once
+CookieAuth is deprecated.
 
 When I print a Comment only part of it is printed. How can I get the rest?
 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -114,7 +117,8 @@ Non-obvious behavior and other need to know
 * All of the listings (list of stories on subreddit, etc.) are generators,
   *not* lists. If you need them to be lists, an easy way is to call ``list()``
   with your variable as the argument.
-* The default limit for fetching Things is 25. You can change this with the
+* The default limit for fetching Things is your `reddit preferences default
+  <https://www.reddit.com/prefs>`_, usually 25. You can change this with the
   ``limit`` param. If want as many Things as you can then set ``limit=None``.
 * We can at most get 1000 results from every listing, this is an upstream
   limitation by reddit. There is nothing we can do to go past this
diff --git a/docs/pages/getting_started.rst b/docs/pages/getting_started.rst
index c8f204e..32bc1fa 100644
--- a/docs/pages/getting_started.rst
+++ b/docs/pages/getting_started.rst
@@ -6,10 +6,9 @@ Getting Started
 In this tutorial we'll go over everything needed to create a bot or program
 using reddit's API through the Python Reddit API Wrapper (PRAW). We're going to
 write a program that breaks down a redditor's karma by subreddit, just like the
-`reddit gold <https://www.reddit.com/gold/about>`_ feature. Unlike that, our
-program can break it down for any redditor, not just us. However, it will be
-less precise due to limitations in the reddit API, but we're getting ahead of
-ourselves.
+reddit feature. Unlike that, our program can break it down for any redditor,
+not just us. However, it will be less precise due to limitations in the reddit
+API, but we're getting ahead of ourselves.
 
 This is a beginners tutorial to PRAW. We'll go over the hows and whys of
 everything from getting started to writing your first program accessing reddit.
diff --git a/docs/pages/oauth.rst b/docs/pages/oauth.rst
index 8699db0..a486fb7 100644
--- a/docs/pages/oauth.rst
+++ b/docs/pages/oauth.rst
@@ -156,6 +156,12 @@ need to refresh the access token.
 This returns a dict, where the ``access_token`` key has had its value updated.
 Neither ``scope`` nor ``refresh_token`` will have changed.
 
+Note: In version 3.2.0 and higher, PRAW will automatically attempt to refresh
+the access token if a refresh token is available when it expires. For most
+personal-use scripts, this eliminates the need to use
+:meth:`.refresh_access_information` except when signing in.
+
+
 .. _oauth_webserver:
 
 An example webserver
diff --git a/docs/pages/useful_scripts.rst b/docs/pages/useful_scripts.rst
index 6250cc0..18886a1 100644
--- a/docs/pages/useful_scripts.rst
+++ b/docs/pages/useful_scripts.rst
@@ -11,6 +11,10 @@ this page to add in more.
     a program useful to subreddit moderators, and ``subreddit_stats``, a tool
     to compute submission / comment statistics for a subreddit.
 
+`prawoauth2`_ by `Avinash Sajjanshetty <https://github.com/avinassh>`_
+    ``prawoauth2`` is a helper library for PRAW which makes writing Reddit bots/apps using 
+    OAuth2 super easy, simple and fun.
+
 `AutoModerator`_ by `Deimos <https://github.com/deimos>`_
     A bot for automating straightforward reddit moderation tasks and improving
     upon the existing spam-filter.
@@ -106,10 +110,10 @@ this page to add in more.
     The bot will then send them a private message with the date they specified.
     `u/RemindMeBot <http://www.reddit.com/user/RemindMeBot>`_.
 
-`Massdrop Multi Bot <https://github.com/darkmio/Massdrop-Reddit-Bot>`_
-    A bot which made Massdrop available for everyone and then grew into an
-    assortment of different fixes for links that regularly get mistakenly used,
-    like Gfycat, Massdrop and Subreddit-Names in titles.
+`RedditRover`_ by `DarkMio <https://github.com/DarkMio>`_
+    A plugin based Reddit Multi Bot Framework intended for new and advanced 
+    programmers to host a wide variety of Reddit bots without mangling with
+    all the ins and outs of Reddit, PRAW and API limitations. 
 
 `Reddit Keyword Tracking Bot`_ by Jermell Beane
     <requires Kivy> A bot that will watch any subreddits and email you with
@@ -134,6 +138,9 @@ this page to add in more.
 `ButtsBot`_ by `Judson Dunaway-Barlow <https://github.com/jadunawa>`_
     A silly bot that posts a picture of a (clothed) butt from the Astros team whenever somebody in the /r/Astros subreddit       uses any of a few certain keywords in a comment.
 
+`GoodReads Bot`_ by `Avinash Sajjanshetty <https://github.com/avinassh>`_
+    A bot which powers `/u/goodreadsbot <https://www.reddit.com/u/goodreadsbot>`_ on Reddit, posts information of a book whenever someone posts a link to Goodreads.
+
 **\<Your Script Here\>**
     Edit `this page on github <https://github.com/praw-dev/praw/blob/master/
     docs/pages/useful_scripts.rst>`_ to add your script to this list.
@@ -146,8 +153,10 @@ this page to add in more.
 .. _`Butcher bot`: https://github.com/xiphirx/Butcher-Bot
 .. _`ButtsBot`: https://github.com/jadunawa/ButtsBot
 .. _`EVE Killmail Reddit Bot`: https://github.com/ArnoldM904/EK_Reddit_Bot
+.. _`GoodReads Bot`: https://github.com/avinassh/Reddit-GoodReads-Bot
 .. _`Groompbot`: https://github.com/AndrewNeo/groompbot
 .. _`NetflixBot`: https://github.com/alanwright/netflixbot
+.. _`prawoauth2`: https://github.com/avinassh/prawoauth2
 .. _`PRAWtools`: https://github.com/praw-dev/prawtools
 .. _`Reddit Keyword Tracking Bot`:
      https://github.com/SwedishBotMafia/RScanBot.Gen
@@ -155,6 +164,7 @@ this page to add in more.
 .. _`Reddit-to-Diigo-Copier`:
      https://github.com/OdinsHat/Reddit-to-Diigo-Copier
 .. _`RedditAgain`: https://github.com/karan/RedditAgain
+.. _`RedditRover` : https://github.com/DarkMio/RedditRover
 .. _`RemindMeBot`: https://github.com/SIlver--/remindmebot-reddit
 .. _`VideoLinkBot`: https://github.com/dmarx/VideoLinkBot
 .. _`newsfrbot`: https://github.com/gardaud/newsfrbot
diff --git a/praw.egg-info/PKG-INFO b/praw.egg-info/PKG-INFO
index 8a51383..0510d87 100644
--- a/praw.egg-info/PKG-INFO
+++ b/praw.egg-info/PKG-INFO
@@ -1,6 +1,6 @@
 Metadata-Version: 1.1
 Name: praw
-Version: 3.2.1
+Version: 3.3.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
@@ -55,7 +55,7 @@ Description: .. _main_page:
         Installation
         ------------
         
-        PRAW works with python 2.6, 2.7, 3.1, 3.2, 3.3, and 3.4. The recommended way to
+        PRAW is supported on python 2.7, 3.3, 3.4 and 3.5. The recommended way to
         install is via `pip <https://pypi.python.org/pypi/pip>`_
         
         .. code-block:: bash
@@ -132,11 +132,8 @@ Classifier: Intended Audience :: Developers
 Classifier: License :: OSI Approved :: GNU General Public License (GPL)
 Classifier: Natural Language :: English
 Classifier: Operating System :: OS Independent
-Classifier: Programming Language :: Python :: 2.6
 Classifier: Programming Language :: Python :: 2.7
-Classifier: Programming Language :: Python :: 3
-Classifier: Programming Language :: Python :: 3.1
-Classifier: Programming Language :: Python :: 3.2
 Classifier: Programming Language :: Python :: 3.3
 Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
 Classifier: Topic :: Utilities
diff --git a/praw.egg-info/SOURCES.txt b/praw.egg-info/SOURCES.txt
index 5ce9b47..cd72d6d 100644
--- a/praw.egg-info/SOURCES.txt
+++ b/praw.egg-info/SOURCES.txt
@@ -43,6 +43,7 @@ tests/__init__.py
 tests/helper.py
 tests/test_authenticated_reddit.py
 tests/test_comments.py
+tests/test_config.py
 tests/test_decorators.py
 tests/test_flair.py
 tests/test_images.py
@@ -111,6 +112,7 @@ tests/cassettes/test_flair_csv_empty.json
 tests/cassettes/test_flair_csv_many.json
 tests/cassettes/test_flair_csv_optional_args.json
 tests/cassettes/test_flair_csv_requires_user.json
+tests/cassettes/test_friends_oauth.json
 tests/cassettes/test_front_page_comment_replies_are_none.json
 tests/cassettes/test_get_access_information.json
 tests/cassettes/test_get_access_information_with_invalid_code.json
@@ -123,6 +125,7 @@ tests/cassettes/test_get_contributors_private.json
 tests/cassettes/test_get_contributors_public.json
 tests/cassettes/test_get_contributors_public_exception.json
 tests/cassettes/test_get_controversial.json
+tests/cassettes/test_get_edited_oauth.json
 tests/cassettes/test_get_flair_list.json
 tests/cassettes/test_get_front_page.json
 tests/cassettes/test_get_hidden.json
@@ -165,6 +168,7 @@ tests/cassettes/test_get_unread_update_has_mail.json
 tests/cassettes/test_get_upvoted_and_downvoted.json
 tests/cassettes/test_get_wiki_page.json
 tests/cassettes/test_get_wiki_pages.json
+tests/cassettes/test_hide_oauth.json
 tests/cassettes/test_ignore_and_unignore_reports.json
 tests/cassettes/test_inbox_comment_permalink.json
 tests/cassettes/test_inbox_comment_replies_are_none.json
@@ -236,6 +240,8 @@ tests/cassettes/test_send_from_subreddit.json
 tests/cassettes/test_send_invalid.json
 tests/cassettes/test_send_privatemessage_oauth.json
 tests/cassettes/test_set_access_credentials.json
+tests/cassettes/test_set_access_credentials_with_list.json
+tests/cassettes/test_set_access_credentials_with_string.json
 tests/cassettes/test_set_individuals_flair_oauth.json
 tests/cassettes/test_set_settings.json
 tests/cassettes/test_set_settings_oauth.json
diff --git a/praw/__init__.py b/praw/__init__.py
index d76a6ae..b07fb05 100644
--- a/praw/__init__.py
+++ b/praw/__init__.py
@@ -50,19 +50,7 @@ from update_checker import update_check
 from warnings import warn_explicit
 
 
-__version__ = '3.2.1'
-
-if os.environ.get('SERVER_SOFTWARE') is not None:
-    # Google App Engine information
-    # https://developers.google.com/appengine/docs/python/
-    PLATFORM_INFO = os.environ.get('SERVER_SOFTWARE')
-else:
-    # Standard platform information
-    PLATFORM_INFO = platform.platform(True)
-
-UA_STRING = '%%s PRAW/%s Python/%s %s' % (__version__,
-                                          sys.version.split()[0],
-                                          PLATFORM_INFO)
+__version__ = '3.3.0'
 
 MIN_IMAGE_SIZE = 128
 MAX_IMAGE_SIZE = 512000
@@ -106,6 +94,7 @@ class Config(object):  # pylint: disable=R0903
                  'domain':              'domain/%s/',
                  'duplicates':          'duplicates/%s/',
                  'edit':                'api/editusertext/',
+                 'edited':              'r/%s/about/edited/',
                  'flair':               'api/flair/',
                  'flairconfig':         'api/flairconfig/',
                  'flaircsv':            'api/flaircsv/',
@@ -113,6 +102,7 @@ class Config(object):  # pylint: disable=R0903
                  'flairselector':       'api/flairselector/',
                  'flairtemplate':       'api/flairtemplate/',
                  'friend':              'api/friend/',
+                 'friend_v1':           'api/v1/me/friends/{user}',
                  'friends':             'prefs/friends/',
                  'gild_thing':          'api/v1/gold/gild/{fullname}/',
                  'gild_user':           'api/v1/gold/give/{username}/',
@@ -202,6 +192,24 @@ class Config(object):  # pylint: disable=R0903
                  'wiki_contributors':   'r/%s/about/wikicontributors/'}
     WWW_PATHS = set(['authorize'])
 
+    @staticmethod
+    def ua_string(praw_info):
+        """Return the user-agent string.
+
+        The user-agent string contains PRAW version and platform version info.
+
+        """
+        if os.environ.get('SERVER_SOFTWARE') is not None:
+            # Google App Engine information
+            # https://developers.google.com/appengine/docs/python/
+            info = os.environ.get('SERVER_SOFTWARE')
+        else:
+            # Standard platform information
+            info = platform.platform(True).encode('ascii', 'ignore')
+
+        return '{0} PRAW/{1} Python/{2} {3}'.format(
+            praw_info, __version__, sys.version.split()[0], info)
+
     def __init__(self, site_name, **kwargs):
         """Initialize PRAW's configuration."""
         def config_boolean(item):
@@ -251,7 +259,7 @@ class Config(object):  # pylint: disable=R0903
         self.refresh_token = obj.get('oauth_refresh_token') or None
         self.store_json_result = config_boolean(obj.get('store_json_result'))
 
-        if 'short_domain' in obj:
+        if 'short_domain' in obj and obj['short_domain']:
             self._short_domain = 'http://' + obj['short_domain']
         else:
             self._short_domain = None
@@ -332,7 +340,7 @@ class BaseReddit(object):
                              **kwargs)
         self.handler = handler or DefaultHandler()
         self.http = Session()
-        self.http.headers['User-Agent'] = UA_STRING % user_agent
+        self.http.headers['User-Agent'] = self.config.ua_string(user_agent)
         self.http.validate_certs = self.config.validate_certs
 
         # This `Session` object is only used to store request information that
@@ -605,6 +613,12 @@ class BaseReddit(object):
         hook = self._json_reddit_objecter if as_objects else None
         # Request url just needs to be available for the objecter to use
         self._request_url = url  # pylint: disable=W0201
+
+        if response == '':
+            # Some of the v1 urls don't return anything, even when they're
+            # successful.
+            return response
+
         data = json.loads(response, object_hook=hook)
         delattr(self, '_request_url')
         # Update the modhash
@@ -690,7 +704,9 @@ class OAuth2Reddit(BaseReddit):
         ``redirect_uri``.
 
         """
-        return all((self.client_id, self.client_secret, self.redirect_uri))
+        return all((self.client_id is not None,
+                    self.client_secret is not None,
+                    self.redirect_uri is not None))
 
     @decorators.require_oauth
     def refresh_access_information(self, refresh_token):
@@ -1206,7 +1222,7 @@ class AuthenticatedReddit(OAuth2Reddit, UnauthenticatedReddit):
         elif self._authentication:
             return 'LoggedIn reddit session (user: {0})'.format(self.user)
         else:
-            return 'Unauthenticated reddit sesssion'
+            return 'Unauthenticated reddit session'
 
     def _url_update(self, url):
         # When getting posts from a multireddit owned by the authenticated
@@ -1314,6 +1330,12 @@ class AuthenticatedReddit(OAuth2Reddit, UnauthenticatedReddit):
         data = {'r':  six.text_type(subreddit), 'link': link}
         return self.request_json(self.config['flairselector'], data=data)
 
+    @decorators.restrict_access(scope='read', login=True)
+    def get_friends(self, **params):
+        """Return a UserList of Redditors with whom the user is friends."""
+        url = self.config['friends']
+        return self.request_json(url, params=params)[0]
+
     @decorators.restrict_access(scope='identity', oauth_only=True)
     def get_me(self):
         """Return a LoggedInRedditor object.
@@ -1387,7 +1409,8 @@ class AuthenticatedReddit(OAuth2Reddit, UnauthenticatedReddit):
             pswd = password or self.config.pswd
         if not pswd:
             import getpass
-            pswd = getpass.getpass('Password for %s: ' % user)
+            pswd = getpass.getpass('Password for {0}: '.format(user)
+                                   .encode('ascii', 'ignore'))
 
         data = {'passwd': pswd,
                 'user': user}
@@ -1469,6 +1492,10 @@ class AuthenticatedReddit(OAuth2Reddit, UnauthenticatedReddit):
             identity scopes
 
         """
+        if isinstance(scope, (list, tuple)):
+            scope = set(scope)
+        elif isinstance(scope, six.string_types):
+            scope = set(scope.split())
         if not isinstance(scope, set):
             raise TypeError('`scope` parameter must be a set')
         self.clear_authentication()
@@ -1841,7 +1868,7 @@ class ModLogMixin(AuthenticatedReddit):
         """Return a get_content generator for moderation log items.
 
         :param subreddit: Either a Subreddit object or the name of the
-            subreddit to return the flair list for.
+            subreddit to return the modlog for.
         :param mod: If given, only return the actions made by this moderator.
             Both a moderator name or Redditor object can be used here.
         :param action: If given, only return entries for the specified action.
@@ -1897,7 +1924,7 @@ class ModOnlyMixin(AuthenticatedReddit):
         """
         Return a get_content generator of contributors for the given subreddit.
 
-        If it's a public subreddit, then user/pswd authentication as a
+        If it's a public subreddit, then authentication as a
         moderator of the subreddit is required. For protected/private
         subreddits only access is required. See issue #246.
 
@@ -1919,6 +1946,21 @@ class ModOnlyMixin(AuthenticatedReddit):
                 return decorator(get_contributors_helper)(self, subreddit)
         return get_contributors_helper(self, subreddit)
 
+    @decorators.restrict_access(scope='read', mod=True)
+    def get_edited(self, subreddit='mod', *args, **kwargs):
+        """Return a get_content generator of edited items.
+
+        :param subreddit: Either a Subreddit object or the name of the
+            subreddit to return the edited items for. Defaults to `mod` which
+            includes items for all the subreddits you moderate.
+
+        The additional parameters are passed directly into
+        :meth:`.get_content`. Note: the `url` parameter cannot be altered.
+
+        """
+        return self.get_content(self.config['edited'] %
+                                six.text_type(subreddit), *args, **kwargs)
+
     @decorators.restrict_access(scope='privatemessages', mod=True)
     def get_mod_mail(self, subreddit='mod', *args, **kwargs):
         """Return a get_content generator for moderator messages.
@@ -1936,10 +1978,10 @@ class ModOnlyMixin(AuthenticatedReddit):
 
     @decorators.restrict_access(scope='read', mod=True)
     def get_mod_queue(self, subreddit='mod', *args, **kwargs):
-        """Return a get_content_generator for the moderator queue.
+        """Return a get_content generator for the moderator queue.
 
         :param subreddit: Either a Subreddit object or the name of the
-            subreddit to return the flair list for. Defaults to `mod` which
+            subreddit to return the modqueue for. Defaults to `mod` which
             includes items for all the subreddits you moderate.
 
         The additional parameters are passed directly into
@@ -1951,10 +1993,10 @@ class ModOnlyMixin(AuthenticatedReddit):
 
     @decorators.restrict_access(scope='read', mod=True)
     def get_reports(self, subreddit='mod', *args, **kwargs):
-        """Return a get_content generator of reported submissions.
+        """Return a get_content generator of reported items.
 
         :param subreddit: Either a Subreddit object or the name of the
-            subreddit to return the flair list for. Defaults to `mod` which
+            subreddit to return the reported items. Defaults to `mod` which
             includes items for all the subreddits you moderate.
 
         The additional parameters are passed directly into
@@ -1969,8 +2011,8 @@ class ModOnlyMixin(AuthenticatedReddit):
         """Return a get_content generator of spam-filtered items.
 
         :param subreddit: Either a Subreddit object or the name of the
-            subreddit to return the flair list for. Defaults to `mod` which
-            includes items for all the subreddits you moderate.
+            subreddit to return the spam-filtered items for. Defaults to `mod`
+            which includes items for all the subreddits you moderate.
 
         The additional parameters are passed directly into
         :meth:`.get_content`. Note: the `url` parameter cannot be altered.
@@ -1988,11 +2030,11 @@ class ModOnlyMixin(AuthenticatedReddit):
 
     @decorators.restrict_access(scope='read', mod=True)
     def get_unmoderated(self, subreddit='mod', *args, **kwargs):
-        """Return a get_content generator of unmoderated items.
+        """Return a get_content generator of unmoderated submissions.
 
         :param subreddit: Either a Subreddit object or the name of the
-            subreddit to return the flair list for. Defaults to `mod` which
-            includes items for all the subreddits you moderate.
+            subreddit to return the unmoderated submissions for. Defaults to
+            `mod` which includes items for all the subreddits you moderate.
 
         The additional parameters are passed directly into
         :meth:`.get_content`. Note: the `url` parameter cannot be altered.
@@ -2480,7 +2522,10 @@ class ReportMixin(AuthenticatedReddit):
         data = {'id': thing_id,
                 'executed': method}
         response = self.request_json(self.config[method], data=data)
-        self.evict(urljoin(self.user._url, 'hidden'))  # pylint: disable=W0212
+
+        if self.user is not None:
+            self.evict(urljoin(self.user._url,  # pylint: disable=W0212
+                               'hidden'))
         return response
 
     def unhide(self, thing_id):
diff --git a/praw/errors.py b/praw/errors.py
index f41a6f6..b869f78 100644
--- a/praw/errors.py
+++ b/praw/errors.py
@@ -41,12 +41,14 @@ class ClientException(PRAWException):
 
     """Base exception class for errors that don't involve the remote API."""
 
-    def __init__(self, message):
+    def __init__(self, message=None):
         """Construct a ClientException.
 
-        :params message: The error message to display.
+        :param message: The error message to display.
 
         """
+        if not message:
+            message = 'Clientside error'
         super(ClientException, self).__init__()
         self.message = message
 
@@ -68,7 +70,6 @@ class OAuthScopeRequired(ClientException):
 
         :param function: The function that requires a scope.
         :param scope: The scope required for the function.
-
         :param message: A custom message to associate with the
             exception. Default: `function` requires the OAuth2 scope `scope`
 
@@ -136,8 +137,9 @@ class ModeratorRequired(LoginRequired):
         :param function: The function that requires moderator access.
 
         """
-        msg = '`{0}` requires a moderator of the subreddit'.format(function)
-        super(ModeratorRequired, self).__init__(msg)
+        message = ('`{0}` requires a moderator '
+                   'of the subreddit').format(function)
+        super(ModeratorRequired, self).__init__(message)
 
 
 class ModeratorOrScopeRequired(LoginOrScopeRequired, ModeratorRequired):
@@ -176,15 +178,22 @@ class HTTPException(PRAWException):
 
     """Base class for HTTP related exceptions."""
 
-    def __init__(self, _raw):
-        """Construct a ClientException.
+    def __init__(self, _raw, message=None):
+        """Construct a HTTPException.
 
         :params _raw: The internal request library response object. This object
             is mapped to attribute `_raw` whose format may change at any time.
 
         """
+        if not message:
+            message = 'HTTP error'
         super(HTTPException, self).__init__()
         self._raw = _raw
+        self.message = message
+
+    def __str__(self):
+        """Return the message of the error."""
+        return self.message
 
 
 class Forbidden(HTTPException):
@@ -203,6 +212,10 @@ class InvalidComment(PRAWException):
 
     ERROR_TYPE = 'DELETED_COMMENT'
 
+    def __str__(self):
+        """Return the message of the error."""
+        return self.ERROR_TYPE
+
 
 class InvalidSubmission(PRAWException):
 
@@ -210,6 +223,10 @@ class InvalidSubmission(PRAWException):
 
     ERROR_TYPE = 'DELETED_LINK'
 
+    def __str__(self):
+        """Return the message of the error."""
+        return self.ERROR_TYPE
+
 
 class InvalidSubreddit(PRAWException):
 
@@ -217,23 +234,34 @@ class InvalidSubreddit(PRAWException):
 
     ERROR_TYPE = 'SUBREDDIT_NOEXIST'
 
+    def __str__(self):
+        """Return the message of the error."""
+        return self.ERROR_TYPE
+
 
 class RedirectException(PRAWException):
 
     """Raised when a redirect response occurs that is not expected."""
 
-    def __init__(self, request_url, response_url):
+    def __init__(self, request_url, response_url, message=None):
         """Construct a RedirectException.
 
         :param request_url: The url requested.
         :param response_url: The url being redirected to.
+        :param message: A custom message to associate with the exception.
 
         """
-        super(RedirectException, self).__init__(
-            'Unexpected redirect from {0} to {1}'
-            .format(request_url, response_url))
+        if not message:
+            message = ('Unexpected redirect '
+                       'from {0} to {1}').format(request_url, response_url)
+        super(RedirectException, self).__init__()
         self.request_url = request_url
         self.response_url = response_url
+        self.message = message
+
+    def __str__(self):
+        """Return the message of the error."""
+        return self.message
 
 
 class OAuthException(PRAWException):
diff --git a/praw/helpers.py b/praw/helpers.py
index 9d55d87..42f9762 100644
--- a/praw/helpers.py
+++ b/praw/helpers.py
@@ -24,6 +24,7 @@ from __future__ import unicode_literals
 import six
 import sys
 import time
+from collections import deque
 from functools import partial
 from timeit import default_timer as timer
 from praw.errors import HTTPException
@@ -129,7 +130,7 @@ def _stream_generator(get_function, limit=None, verbosity=1):
         start = timer()
         try:
             i = None
-            params = {'count': count}
+            params = {'uniq': count}
             count = (count + 1) % 100
             if before:
                 params['before'] = before
@@ -235,15 +236,14 @@ def flatten_tree(tree, nested_attr='replies', depth_first=False):
         rather than the default breadth-first manner.
 
     """
-    stack = tree[:]
+    stack = deque(tree)
+    extend = stack.extend if depth_first else stack.extendleft
     retval = []
     while stack:
-        item = stack.pop(0)
+        item = stack.popleft()
         nested = getattr(item, nested_attr, None)
-        if nested and depth_first:
-            stack.extend(nested)
-        elif nested:
-            stack[0:0] = nested
+        if nested:
+            extend(nested)
         retval.append(item)
     return retval
 
diff --git a/praw/internal.py b/praw/internal.py
index 8aa67ce..1e3963c 100644
--- a/praw/internal.py
+++ b/praw/internal.py
@@ -158,10 +158,15 @@ def _prepare_request(reddit_session, url, params, data, auth, files,
     # Most POST requests require adding `api_type` and `uh` to the data.
     if data is True:
         data = {}
-    if not auth:
-        data.setdefault('api_type', 'json')
-        if reddit_session.modhash:
-            data.setdefault('uh', reddit_session.modhash)
+
+    if isinstance(data, dict):
+        if not auth:
+            data.setdefault('api_type', 'json')
+            if reddit_session.modhash:
+                data.setdefault('uh', reddit_session.modhash)
+    else:
+        request.headers.setdefault('Content-Type', 'application/json')
+
     request.data = data
     request.files = files
     return request
diff --git a/praw/objects.py b/praw/objects.py
index f1e27f4..51ea08a 100644
--- a/praw/objects.py
+++ b/praw/objects.py
@@ -137,11 +137,13 @@ class RedditContentObject(object):
         # b) The object is not a WikiPage and the reddit_session has the
         #    `read` scope.
         prev_use_oauth = self.reddit_session._use_oauth
-        self.reddit_session._use_oauth = (
-            isinstance(self, WikiPage) and
-            self.reddit_session.has_scope('wikiread')) or \
-            (not isinstance(self, WikiPage) and
-                self.reddit_session.has_scope('read'))
+
+        wiki_page = isinstance(self, WikiPage)
+        scope = self.reddit_session.has_scope
+
+        self.reddit_session._use_oauth = wiki_page and scope('wikiread') or \
+            not wiki_page and scope('read')
+
         try:
             params = {'uniq': self._uniq} if self._uniq else {}
             response = self.reddit_session.request_json(
@@ -355,7 +357,6 @@ class Hideable(RedditContentObject):
 
     """Interface for objects that can be hidden."""
 
-    @restrict_access(scope='report')
     def hide(self, _unhide=False):
         """Hide object in the context of the logged in user.
 
@@ -433,6 +434,11 @@ class Refreshable(RedditContentObject):
         automatically be refreshed serverside. Refreshing a submission will
         also refresh all its comments.
 
+        In the rare case of a submissions's comment[0] being deleted or
+        removed in between its original retrieval and refresh, or
+        inconsistencies between different endpoints resulting in this,
+        an IndexError will be thrown.
+
         """
         unique = self.reddit_session._unique_count  # pylint: disable=W0212
         self.reddit_session._unique_count += 1  # pylint: disable=W0212
@@ -650,7 +656,14 @@ class Comment(Editable, Gildable, Inboxable, Moderatable, Refreshable,
 
     @property
     def replies(self):
-        """Return a list of the comment replies to this comment."""
+        """Return a list of the comment replies to this comment.
+
+        If the comment is not from a submission, :meth:`replies` will
+        always be an empty list unless you call :meth:`refresh()
+        before calling :meth:`replies` due to a limitation in
+        reddit's API.
+
+        """
         if self._replies is None or not self._has_fetched_replies:
             response = self.reddit_session.request_json(self._fast_permalink)
             if not response[1]['data']['children']:
@@ -841,14 +854,38 @@ class Redditor(Gildable, Messageable, Refreshable):
             self._case_name = self.name
             self.name = tmp
 
-    def friend(self):
+    @restrict_access(scope='subscribe')
+    def friend(self, note=None, _unfriend=False):
         """Friend the user.
 
+        :param note: A personal note about the user. Requires reddit Gold.
+        :param _unfriend: Unfriend the user. Please use :meth:`unfriend`
+            instead of setting this parameter manually.
+
         :returns: The json response from the server.
 
         """
         self.reddit_session.evict(self.reddit_session.config['friends'])
-        return _modify_relationship('friend')(self.reddit_session.user, self)
+
+        # Requests through password auth use /api/friend
+        # Requests through oauth use /api/v1/me/friends/%username%
+        if not self.reddit_session.is_oauth_session():
+            modifier = _modify_relationship('friend', unlink=_unfriend)
+            data = {'note': note} if note else {}
+            return modifier(self.reddit_session.user, self, **data)
+
+        url = self.reddit_session.config['friend_v1'].format(user=self.name)
+        # This endpoint wants the data to be a string instead of an actual
+        # dictionary, although it is not required to have any content for adds.
+        # Unfriending does require the 'id' key.
+        if _unfriend:
+            data = {'id': self.name}
+        else:
+            # We cannot send a null or empty note string.
+            data = {'note': note} if note else {}
+        data = dumps(data)
+        method = 'DELETE' if _unfriend else 'PUT'
+        return self.reddit_session.request_json(url, data=data, method=method)
 
     def get_disliked(self, *args, **kwargs):
         """Return a listing of the Submissions the user has downvoted.
@@ -879,6 +916,20 @@ class Redditor(Gildable, Messageable, Refreshable):
         kwargs['_use_oauth'] = self.reddit_session.is_oauth_session()
         return _get_redditor_listing('downvoted')(self, *args, **kwargs)
 
+    @restrict_access(scope='mysubreddits')
+    def get_friend_info(self):
+        """Return information about this friend, including personal notes.
+
+        The personal note can be added or overwritten with :meth:friend, but
+            only if the user has reddit Gold.
+
+        :returns: The json response from the server.
+
+        """
+        url = self.reddit_session.config['friend_v1'].format(user=self.name)
... 559 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