Repository: praw-dev/praw

Issue:
Non-Lazy Loading of Multireddits Fails
Example Code:

``` python
In [1]: import praw

In [2]: reddit = praw.Reddit(user_agent='example')

In [3]: reddit.get_subreddit('linux+opensource', fetch=True)
---------------------------------------------------------------------------
HTTPError                                 Traceback (most recent call last)
<ipython-input-4-ebfe79b7397d> in <module>()
----> 1 reddit.get_subreddit('linux+opensource', fetch=True)

/usr/local/lib/python2.7/dist-packages/praw/__init__.pyc in get_subreddit(self, subreddit_name, *args, **kwargs)
   1015         elif sr_name_lower == 'randnsfw':
   1016             return self.get_random_subreddit(nsfw=True)
-> 1017         return objects.Subreddit(self, subreddit_name, *args, **kwargs)
   1018
   1019     def get_subreddit_recommendations(self, subreddits, omit=None):

/usr/local/lib/python2.7/dist-packages/praw/objects.pyc in __init__(self, reddit_session, subreddit_name, json_dict, fetch)
   1348         info_url = reddit_session.config['subreddit_about'] % subreddit_name
   1349         super(Subreddit, self).__init__(reddit_session, json_dict, fetch,
-> 1350                                         info_url)
   1351         self.display_name = subreddit_name
   1352         self._url = reddit_session.config['subreddit'] % subreddit_name

/usr/local/lib/python2.7/dist-packages/praw/objects.pyc in __init__(self, reddit_session, json_dict, fetch, info_url, underscore_names)
     71         self.reddit_session = reddit_session
     72         self._underscore_names = underscore_names
---> 73         self.has_fetched = self._populate(json_dict, fetch)
     74
     75     def __eq__(self, other):

/usr/local/lib/python2.7/dist-packages/praw/objects.pyc in _populate(self, json_dict, fetch)
    131     def _populate(self, json_dict, fetch):
    132         if json_dict is None:
--> 133             json_dict = self._get_json_dict() if fetch else {}
    134         self.json_dict = (json_dict if
    135                           self.reddit_session.config.store_json_result

/usr/local/lib/python2.7/dist-packages/praw/objects.pyc in _get_json_dict(self)
    124         try:
    125             response = self.reddit_session.request_json(self._info_url,
--> 126                                                         as_objects=False)
    127         finally:
    128             self.reddit_session._use_oauth = prev_use_oauth

/usr/local/lib/python2.7/dist-packages/praw/decorators.pyc in wrapped(reddit_session, *args, **kwargs)
    161     def wrapped(reddit_session, *args, **kwargs):
    162         try:
--> 163             return_value = function(reddit_session, *args, **kwargs)
    164         except HTTPError as exc:
    165             if exc.response.status_code != 400:

/usr/local/lib/python2.7/dist-packages/praw/__init__.pyc in request_json(self, url, params, data, as_objects, retry_on_error)
    555             url += '.json'
    556         response = self._request(url, params, data,
--> 557                                  retry_on_error=retry_on_error)
    558         hook = self._json_reddit_objecter if as_objects else None
    559         # Request url just needs to be available for the objecter to use

/usr/local/lib/python2.7/dist-packages/praw/__init__.pyc in _request(self, url, params, data, files, auth, timeout, raw_response, retry_on_error)
    397             try:
    398                 response = handle_redirect()
--> 399                 _raise_response_exceptions(response)
    400                 self.http.cookies.update(response.cookies)
    401                 if raw_response:

/usr/local/lib/python2.7/dist-packages/praw/internal.pyc in _raise_response_exceptions(response)
    176         else:
    177             raise OAuthException(msg, response.url)
--> 178     response.raise_for_status()
    179
    180

/usr/lib/python2.7/dist-packages/requests/models.pyc in raise_for_status(self)
    771
    772         if http_error_msg:
--> 773             raise HTTPError(http_error_msg, response=self)
    774
    775     def close(self):

HTTPError: 404 Client Error: Not Found

In [4]: reddit.get_subreddit('linux+opensource', fetch=False)
Out[4]: Subreddit(subreddit_name='linux+opensource')
```

Originally discovered over [here](https://github.com/michael-lazar/rtv/issues/15)



Discussion:
Thanks for filing!

After a bit more research, it would appear that this is actually a bug in reddit's api. I added a simple print function to help debug this and noticed that both with and without lazy loading it loads the same URL. In this case, the URL being loaded is `https://api.reddit.com/r/linux+opensource/about/.json`. Loading that URL gives `{"error": 404}`. 

Based off of that I would say that this is actually a bug in reddit's api (since it does not return about.json for multireddits). 

However, this also exposes a slightly different bug in praw. In this case, the issue is that praw does return any error when it receives a 404 error on a lazily loaded object. More information to come later (once I figure out why praw doesn't throw a `HTTPError: 404 Client Error: Not Found` exception when subreddits are lazily loaded). 

After a bit more work, I may file a bug report with reddit. Instead of returning a 404 error, reddit's api really should not return a 404 for multireddits. Instead it should just return a blank sidebar. 


Resolving patch:
diff --git a/praw/objects.py b/praw/objects.py
--- a/praw/objects.py
+++ b/praw/objects.py
@@ -29,6 +29,7 @@
 from heapq import heappop, heappush
 from json import dumps
 from requests.compat import urljoin
+from warnings import warn_explicit
 from praw import (AuthenticatedReddit as AR, ModConfigMixin as MCMix,
                   ModFlairMixin as MFMix, ModLogMixin as MLMix,
                   ModOnlyMixin as MOMix, ModSelfMixin as MSMix,
@@ -1438,6 +1439,11 @@ def __init__(self, reddit_session, subreddit_name=None, json_dict=None,
         if not subreddit_name:
             subreddit_name = json_dict['url'].split('/')[2]
 
+        if fetch and ('+' in subreddit_name or '-' in subreddit_name):
+            fetch = False
+            warn_explicit('fetch=True has no effect on multireddits',
+                          UserWarning, '', 0)
+
         info_url = reddit_session.config['subreddit_about'] % subreddit_name
         self._case_name = subreddit_name
         super(Subreddit, self).__init__(reddit_session, json_dict, fetch,


Test patch:
diff --git a/tests/cassettes/test_multiple_subreddit__fetch.json b/tests/cassettes/test_multiple_subreddit__fetch.json
new file mode 100644
--- /dev/null
+++ b/tests/cassettes/test_multiple_subreddit__fetch.json
@@ -0,0 +1 @@
+{"http_interactions": [{"request": {"body": {"string": "passwd=1111&user=PyAPITestUser2&api_type=json", "encoding": "utf-8"}, "headers": {"Content-Length": ["45"], "Accept-Encoding": ["gzip, deflate"], "Accept": ["*/*"], "User-Agent": ["PRAW_test_suite PRAW/3.1.0 Python/2.7.9 Darwin-14.3.0-x86_64-i386-64bit"], "Connection": ["keep-alive"], "Content-Type": ["application/x-www-form-urlencoded"]}, "method": "POST", "uri": "https://api.reddit.com/api/login/.json"}, "response": {"body": {"base64_string": "H4sIAAAAAAAAAxzLQYrDMAxA0asYrV2woshWfI7uhqE4skwyM61Lkl3J3Utn+x//BT97f0B2L7Bt69sO2X19ewe1HOU/P8zqbTmO54da+dvNO7j3upR9gexg4nWMdn+uEVmaKmFIgakOyWbS0LSilFl1TJR4rrFN4B1o77+rff5EYZCY/BCQLyFdEK/IGVMe2RsFYjaZogjWUEhl1GYyFBQt2MQahTojnOf5BgAA//8DACK2tq3KAAAA", "encoding": "UTF-8"}, "headers": {"x-xss-protection": ["1; mode=block"], "x-content-type-options": ["nosniff"], "content-encoding": ["gzip"], "transfer-encoding": ["chunked"], "set-cookie": ["__cfduid=d7463c43e9dc7a1e29a46680d0eea379d1436653064; expires=Sun, 10-Jul-16 22:17:44 GMT; path=/; domain=.reddit.com; HttpOnly", "secure_session=; Domain=reddit.com; Max-Age=-1436653065; Path=/; expires=Thu, 01-Jan-1970 00:00:01 GMT; HttpOnly", "reddit_session=7302867%2C2015-07-11T15%3A17%3A45%2Ce30355e896881d0a3c84cfe82a18ca1f8ef30db1; Domain=reddit.com; Path=/; HttpOnly"], "server": ["cloudflare-nginx"], "connection": ["keep-alive"], "x-ua-compatible": ["IE=edge"], "pragma": ["no-cache"], "cache-control": ["private, no-cache", "no-cache"], "date": ["Sat, 11 Jul 2015 22:17:45 GMT"], "x-frame-options": ["SAMEORIGIN"], "x-moose": ["majestic"], "content-type": ["application/json; charset=UTF-8"], "cf-ray": ["2047e15682a11ea7-SJC"]}, "status": {"message": "OK", "code": 200}, "url": "https://api.reddit.com/api/login/.json"}, "recorded_at": "2015-07-11T22:17:45"}], "recorded_with": "betamax/0.4.2"}
\ No newline at end of file
diff --git a/tests/test_subreddit.py b/tests/test_subreddit.py
--- a/tests/test_subreddit.py
+++ b/tests/test_subreddit.py
@@ -1,6 +1,7 @@
 """Tests for Subreddit class."""
 
 from __future__ import print_function, unicode_literals
+import warnings
 from praw import errors
 from praw.objects import Subreddit
 from six import text_type
@@ -76,6 +77,13 @@ def test_get_subreddit_recommendations(self):
         self.assertTrue(result)
         self.assertTrue(all(isinstance(x, Subreddit) for x in result))
 
+    @betamax()
+    def test_multiple_subreddit__fetch(self):
+        with warnings.catch_warnings(record=True) as w:
+            self.r.get_subreddit('python+redditdev', fetch=True)
+            assert len(w) == 1
+            assert isinstance(w[0].message, UserWarning)
+
     @betamax()
     def test_subreddit_refresh(self):
         new_description = 'Description {0}'.format(self.r.modhash)