Repository: praw-dev/praw

Issue:
reddit.user.me() when reddit.read_only is True raises RecursionError
## Issue Description

	$ python3
	Python 3.7.0 (default, Jun 29 2018, 20:13:53)
	>>> import praw
	>>> praw.__version__
	'6.0.0.dev0'
	>>> reddit = praw.Reddit(...)
	>>> reddit.read_only = True
	>>> reddit.read_only
	True
	>>> reddit.user.me()
	Traceback (most recent call last):
	  File "<stdin>", line 1, in <module>
	  File "/usr/local/lib/python3.7/site-packages/praw/models/user.py", line 96, in me
	    self._me = Redditor(self._reddit, _data=user_data)
	  File "/usr/local/lib/python3.7/site-packages/praw/models/reddit/redditor.py", line 62, in __init__
	    self._path = API_PATH['user'].format(user=self)
	  File "/usr/local/lib/python3.7/site-packages/praw/models/reddit/base.py", line 64, in __str__
	    return getattr(self, self.STR_FIELD)
	  File "/usr/local/lib/python3.7/site-packages/praw/models/reddit/base.py", line 38, in __getattr__
	    self._fetch()
	  File "/usr/local/lib/python3.7/site-packages/praw/models/reddit/base.py", line 72, in _fetch
	    other = self._reddit.get(self._info_path(),
	  File "/usr/local/lib/python3.7/site-packages/praw/models/reddit/redditor.py", line 66, in _info_path
	    return API_PATH['user_about'].format(user=self)
	  File "/usr/local/lib/python3.7/site-packages/praw/models/reddit/base.py", line 64, in __str__
	    return getattr(self, self.STR_FIELD)
	  File "/usr/local/lib/python3.7/site-packages/praw/models/reddit/base.py", line 38, in __getattr__
	    self._fetch()
	  File "/usr/local/lib/python3.7/site-packages/praw/models/reddit/base.py", line 72, in _fetch
	    other = self._reddit.get(self._info_path(),
	  File "/usr/local/lib/python3.7/site-packages/praw/models/reddit/redditor.py", line 66, in _info_path
	    return API_PATH['user_about'].format(user=self)
	  File "/usr/local/lib/python3.7/site-packages/praw/models/reddit/base.py", line 64, in __str__
	    return getattr(self, self.STR_FIELD)
	  File "/usr/local/lib/python3.7/site-packages/praw/models/reddit/base.py", line 38, in __getattr__
	    self._fetch()
	  File "/usr/local/lib/python3.7/site-packages/praw/models/reddit/base.py", line 72, in _fetch
	    other = self._reddit.get(self._info_path(),

	  [snip]

	  File "/usr/local/lib/python3.7/site-packages/praw/models/reddit/redditor.py", line 66, in _info_path
	    return API_PATH['user_about'].format(user=self)
	  File "/usr/local/lib/python3.7/site-packages/praw/models/reddit/base.py", line 64, in __str__
	    return getattr(self, self.STR_FIELD)
	  File "/usr/local/lib/python3.7/site-packages/praw/models/reddit/base.py", line 38, in __getattr__
	    self._fetch()
	  File "/usr/local/lib/python3.7/site-packages/praw/models/reddit/base.py", line 72, in _fetch
	    other = self._reddit.get(self._info_path(),
	  File "/usr/local/lib/python3.7/site-packages/praw/models/reddit/redditor.py", line 66, in _info_path
	    return API_PATH['user_about'].format(user=self)
	  File "/usr/local/lib/python3.7/site-packages/praw/models/reddit/base.py", line 64, in __str__
	    return getattr(self, self.STR_FIELD)
	RecursionError: maximum recursion depth exceeded
	>>>

It seems `Redditor.__init__` has failed to initialize the instance properly. [`Redditor.__init__`](https://github.com/praw-dev/praw/blob/dfdab9bc004916b458ac9113d29e433f5fd649cb/praw/models/reddit/redditor.py#L49-L62) expects (deserialized) JSON which contain 'name' attribute in top level, but reddit returns JSON which doesn't contain the attribute if the reddit instance is in read_only mode.

Using reddit.user.me() in read_only (i.e. logged-out or userless) mode doesn't make much sense. Although reddit returns 200 and non-error JSON, I think it would be okay to prevent users calling `user.me()` in read_only mode with raising some exception or return None. (If a user really wants the JSON, 
it could be retrieved with `reddit.request("GET", "api/v1/me")`.) Any thoughts?

## System Information

- PRAW Version: 6.0.0.dev0
- Python Version: 3.7.0
- Operating System: OS X El Capitan 10.11.6



Discussion:
> I think it would be okay to prevent users calling user.me() in read_only mode with raising some exception or return None.

I think that's a great fix. Ideally, fixing the issue resulting in recursion would be good too, but returning `None` sounds like a response that makes sense.

Resolving patch:
diff --git a/praw/models/reddit/redditor.py b/praw/models/reddit/redditor.py
--- a/praw/models/reddit/redditor.py
+++ b/praw/models/reddit/redditor.py
@@ -55,6 +55,9 @@ def __init__(self, reddit, name=None, _data=None):
         """
         if bool(name) == bool(_data):
             raise TypeError('Either `name` or `_data` must be provided.')
+        if _data:
+            assert isinstance(_data, dict) and 'name' in _data, \
+                   'Please file a bug with PRAW'
         super(Redditor, self).__init__(reddit, _data)
         self._listing_use_sort = True
         if name:
diff --git a/praw/models/user.py b/praw/models/user.py
--- a/praw/models/user.py
+++ b/praw/models/user.py
@@ -83,6 +83,8 @@ def karma(self):
     def me(self, use_cache=True):  # pylint: disable=invalid-name
         """Return a :class:`.Redditor` instance for the authenticated user.
 
+        In :attr:`~praw.Reddit.read_only` mode, this method returns ``None``.
+
         :param use_cache: When true, and if this function has been previously
             called, returned the cached version (default: True).
 
@@ -91,6 +93,8 @@ def me(self, use_cache=True):  # pylint: disable=invalid-name
            instances, however, for distinct authorizations.
 
         """
+        if self._reddit.read_only:
+            return None
         if self._me is None or not use_cache:
             user_data = self._reddit.get(API_PATH['me'])
             self._me = Redditor(self._reddit, _data=user_data)


Test patch:
diff --git a/tests/unit/models/reddit/test_redditor.py b/tests/unit/models/reddit/test_redditor.py
--- a/tests/unit/models/reddit/test_redditor.py
+++ b/tests/unit/models/reddit/test_redditor.py
@@ -30,6 +30,12 @@ def test_construct_failure(self):
             Redditor(self.reddit, 'dummy', {'id': 'dummy'})
         assert str(excinfo.value) == message
 
+        with pytest.raises(AssertionError):
+            Redditor(self.reddit, _data=[{'name': 'dummy'}])
+
+        with pytest.raises(AssertionError):
+            Redditor(self.reddit, _data={'notname': 'dummy'})
+
     def test_fullname(self):
         redditor = Redditor(self.reddit, _data={'name': 'name', 'id': 'dummy'})
         assert redditor.fullname == 't2_dummy'
diff --git a/tests/unit/models/test_user.py b/tests/unit/models/test_user.py
new file mode 100644
--- /dev/null
+++ b/tests/unit/models/test_user.py
@@ -0,0 +1,10 @@
+from praw.models import User
+
+from .. import UnitTest
+
+
+class TestUser(UnitTest):
+    def test_me__in_read_only_mode(self):
+        assert self.reddit.read_only
+        user = User(self.reddit)
+        assert user.me() is None