Skip to content

Commit

Permalink
Merge pull request #455 from voussoir/praw-leavemod
Browse files Browse the repository at this point in the history
08 July 2015 - Fix multi-scope methods; add leave_moderator
  • Loading branch information
bboe committed Jul 11, 2015
2 parents 092cba9 + 9c876a4 commit 5b72e72
Show file tree
Hide file tree
Showing 41 changed files with 203 additions and 91 deletions.
14 changes: 14 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,20 @@ formatted links that link to the relevant place in the code overview.

.. begin_changelog_body
Unreleased
----------
* **[BUGFIX]** Fixed methods which require more than one OAuth scope.
* **[BUGFIX]** Fixed :meth:`praw.objects.WikiPage.remove_editor` raising
AssertionError when used through OAuth.
* **[CHANGE]** :meth:`praw.objects.Refreshable.refresh` will now always return
a fresh object. Previously, Subreddits and Redditors would use cache content
when available.
* **[CHANGE]** :class:`praw.objects.WikiPage` is now refreshable, and will
lazy-load.
* **[FEATURE]** Added methods :meth:`leave_moderator` and
:meth:`leave_contributor` to :class:`praw.__init__.AuthenticatedReddit`
and :class:`praw.objects.Subreddit`.

PRAW 3.1.0
----------
* **[BUGFIX]** Fixed method `get_random_submission` which failed to raise
Expand Down
65 changes: 57 additions & 8 deletions praw/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,8 @@ class Config(object): # pylint: disable=R0903
'ignore_reports': 'api/ignore_reports/',
'inbox': 'message/inbox/',
'info': 'api/info/',
'leavecontributor': 'api/leavecontributor',
'leavemoderator': 'api/leavemoderator',
'login': 'api/login/',
'me': 'api/v1/me',
'mentions': 'message/mentions',
Expand Down Expand Up @@ -1051,11 +1053,9 @@ def get_top(self, *args, **kwargs):
return self.get_content(self.config['top'], *args, **kwargs)

@decorators.restrict_access(scope='wikiread', login=False)
def get_wiki_page(self, subreddit, page, **params):
def get_wiki_page(self, subreddit, page):
"""Return a WikiPage object for the subreddit and page provided."""
return self.request_json(self.config['wiki_page'] %
(six.text_type(subreddit), page.lower()),
params=params)
return objects.WikiPage(self, six.text_type(subreddit), page.lower())

@decorators.restrict_access(scope='wikiread', login=False)
def get_wiki_pages(self, subreddit):
Expand Down Expand Up @@ -1284,8 +1284,11 @@ def get_me(self):
return user

def has_scope(self, scope):
"""Return True if OAuth2 authorized for the passed in scope."""
return self.is_oauth_session() and scope in self._authentication
"""Return True if OAuth2 authorized for the passed in scope(s)."""
if isinstance(scope, six.string_types):
scope = [scope]
return self.is_oauth_session() and all(s in self._authentication
for s in scope)

def is_logged_in(self):
"""Return True when session is authenticated via login."""
Expand Down Expand Up @@ -1962,6 +1965,52 @@ def get_wiki_contributors(self, subreddit, *args, **kwargs):
user_only=True, *args, **kwargs)


class ModSelfMixin(AuthenticatedReddit):

"""Adds methods pertaining to the 'modself' OAuth scope (or login).
You should **not** directly instantiate instances of this class. Use
:class:`.Reddit` instead.
"""

def leave_contributor(self, subreddit):
"""Abdicate approved submitter status in a subreddit. Use with care.
:param subreddit: The name of the subreddit to leave `status` from.
:returns: the json response from the server.
"""
return self._leave_status(subreddit, self.config['leavecontributor'])

def leave_moderator(self, subreddit):
"""Abdicate moderator status in a subreddit. Use with care.
:param subreddit: The name of the subreddit to leave `status` from.
:returns: the json response from the server.
"""
self.evict(self.config['my_mod_subreddits'])
return self._leave_status(subreddit, self.config['leavemoderator'])

@decorators.restrict_access(scope='modself', mod=False)
def _leave_status(self, subreddit, statusurl):
"""Abdicate status in a subreddit.
:param subreddit: The name of the subreddit to leave `status` from.
:param statusurl: The API URL which will be used in the leave request.
Please use :meth:`leave_contributor` or :meth:`leave_moderator`
rather than setting this directly.
:returns: the json response from the server.
"""
if isinstance(subreddit, six.string_types):
subreddit = self.get_subreddit(subreddit)

data = {'id': subreddit.fullname}
return self.request_json(statusurl, data=data)


class MultiredditMixin(AuthenticatedReddit):

"""Adds methods pertaining to multireddits.
Expand Down Expand Up @@ -2500,8 +2549,8 @@ def unsubscribe(self, subreddit):


class Reddit(ModConfigMixin, ModFlairMixin, ModLogMixin, ModOnlyMixin,
MultiredditMixin, MySubredditsMixin, PrivateMessagesMixin,
ReportMixin, SubmitMixin, SubscribeMixin):
ModSelfMixin, MultiredditMixin, MySubredditsMixin,
PrivateMessagesMixin, ReportMixin, SubmitMixin, SubscribeMixin):

"""Provides access to reddit's API.
Expand Down
3 changes: 2 additions & 1 deletion praw/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,8 @@ def is_mod_of_all(user, subreddit):
# Defer access until necessary for RedditContentObject.
# This is because scoped sessions may not require this
# attribute to exist, thus it might not be set.
subreddit = cls if hasattr(cls, '_case_name') else False
from praw.objects import Subreddit
subreddit = cls if isinstance(cls, Subreddit) else False
else:
subreddit = kwargs.get(
'subreddit', args[0] if args else None)
Expand Down
4 changes: 4 additions & 0 deletions praw/internal.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ def _modify_relationship(relationship, unlink=False, is_sub=False):
access = {'scope': None, 'login': True}
elif relationship == 'moderator':
access = {'scope': 'modothers'}
elif relationship in ['banned', 'contributor']:
access = {'scope': 'modcontributors'}
elif relationship in ['wikibanned', 'wikicontributor']:
access = {'scope': ['modcontributors', 'modwiki']}
else:
access = {'scope': None, 'mod': True}

Expand Down
54 changes: 33 additions & 21 deletions praw/objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,9 @@
from requests.compat import urljoin
from praw import (AuthenticatedReddit as AR, ModConfigMixin as MCMix,
ModFlairMixin as MFMix, ModLogMixin as MLMix,
ModOnlyMixin as MOMix, MultiredditMixin as MultiMix,
PrivateMessagesMixin as PMMix, SubmitMixin, SubscribeMixin,
UnauthenticatedReddit as UR)
ModOnlyMixin as MOMix, ModSelfMixin as MSMix,
MultiredditMixin as MultiMix, PrivateMessagesMixin as PMMix,
SubmitMixin, SubscribeMixin, UnauthenticatedReddit as UR)
from praw.decorators import (alias_function, limit_chars, restrict_access,
deprecated)
from praw.errors import ClientException, InvalidComment
Expand Down Expand Up @@ -421,18 +421,17 @@ class Refreshable(RedditContentObject):
def refresh(self):
"""Re-query to update object with latest values. Return the object.
Note that if this call is made within cache_timeout as specified in
praw.ini then this will return the cached content. Any listing, such
as the submissions on a subreddits top page, will automatically be
refreshed serverside. Refreshing a submission will also refresh all its
comments.
Any listing, such as the submissions on a subreddits top page, will
automatically be refreshed serverside. Refreshing a submission will
also refresh all its comments.
"""
unique = self.reddit_session._unique_count # pylint: disable=W0212
self.reddit_session._unique_count += 1 # pylint: disable=W0212
unique = self.reddit_session._unique_count # pylint: disable=W0212

if isinstance(self, Redditor):
other = Redditor(self.reddit_session, self._case_name, fetch=True)
other = Redditor(self.reddit_session, self._case_name, fetch=True,
uniq=unique)
elif isinstance(self, Comment):
sub = Submission.from_url(self.reddit_session, self.permalink,
params={'uniq': unique})
Expand All @@ -447,7 +446,12 @@ def refresh(self):
comment_sort=self._comment_sort,
params=params)
elif isinstance(self, Subreddit):
other = Subreddit(self.reddit_session, self._case_name, fetch=True)
other = Subreddit(self.reddit_session, self._case_name, fetch=True,
uniq=unique)
elif isinstance(self, WikiPage):
other = WikiPage(self.reddit_session,
six.text_type(self.subreddit), self.page,
fetch=True, uniq=unique)

self.__dict__ = other.__dict__ # pylint: disable=W0201
return self
Expand Down Expand Up @@ -787,7 +791,7 @@ class Redditor(Gildable, Messageable, Refreshable):
get_submitted = _get_redditor_listing('submitted')

def __init__(self, reddit_session, user_name=None, json_dict=None,
fetch=False):
fetch=False, **kwargs):
"""Construct an instance of the Redditor object."""
if not user_name:
user_name = json_dict['name']
Expand All @@ -796,7 +800,7 @@ def __init__(self, reddit_session, user_name=None, json_dict=None,
# json_dict 'name' attribute (if available) has precedence
self._case_name = user_name
super(Redditor, self).__init__(reddit_session, json_dict,
fetch, info_url)
fetch, info_url, **kwargs)
self.name = self._case_name
self._url = reddit_session.config['user'] % self.name
self._mod_subs = None
Expand Down Expand Up @@ -1372,6 +1376,8 @@ class Subreddit(Messageable, Refreshable):
('get_wiki_contributors', MOMix),
('get_wiki_page', UR),
('get_wiki_pages', UR),
('leave_contributor', MSMix),
('leave_moderator', MSMix),
('search', UR),
('select_flair', AR),
('set_flair', MFMix),
Expand Down Expand Up @@ -1426,7 +1432,7 @@ class Subreddit(Messageable, Refreshable):
get_top_from_year = _get_sorter('top', t='year')

def __init__(self, reddit_session, subreddit_name=None, json_dict=None,
fetch=False):
fetch=False, **kwargs):
"""Construct an instance of the Subreddit object."""
# Special case for when my_subreddits is called as no name is returned
# so we have to extract the name from the URL. The URLs are returned
Expand All @@ -1437,7 +1443,7 @@ def __init__(self, reddit_session, subreddit_name=None, json_dict=None,
info_url = reddit_session.config['subreddit_about'] % subreddit_name
self._case_name = subreddit_name
super(Subreddit, self).__init__(reddit_session, json_dict, fetch,
info_url)
info_url, **kwargs)
self.display_name = self._case_name
self._url = reddit_session.config['subreddit'] % self.display_name
# '' is the hot listing
Expand Down Expand Up @@ -1519,10 +1525,10 @@ def __init__(self, reddit_session, author=None, name=None,
name = json_dict['path'].split('/')[-1]

info_url = reddit_session.config['multireddit_about'] % (author, name)
super(Multireddit, self).__init__(reddit_session, json_dict, fetch,
info_url, **kwargs)
self.name = name
self._author = author
super(Multireddit, self).__init__(reddit_session, json_dict, fetch,
info_url, **kwargs)
self._url = reddit_session.config['multireddit'] % (author, name)

def __repr__(self):
Expand All @@ -1541,6 +1547,13 @@ def _post_populate(self, fetch):
self.subreddits = [Subreddit(self.reddit_session, item['name'])
for item in self.subreddits]

# paths are of the form "/user/USERNAME/m/MULTINAME"
author = self.path.split('/')
assert 'user' in author and 'm' in author
author = author[2]
assert author.lower() == self._author.lower()
self.author = Redditor(self.reddit_session, author)

@restrict_access(scope='subscribe')
def add_subreddit(self, subreddit, _delete=False, *args, **kwargs):
"""Add a subreddit to the multireddit.
Expand Down Expand Up @@ -1678,7 +1691,7 @@ def _convert(reddit_session, data):
return retval


class WikiPage(RedditContentObject):
class WikiPage(Refreshable):

"""An individual WikiPage object."""

Expand All @@ -1697,15 +1710,15 @@ def from_api_response(cls, reddit_session, json_dict):
return cls(reddit_session, subreddit, page, json_dict=json_dict)

def __init__(self, reddit_session, subreddit=None, page=None,
json_dict=None, fetch=True):
json_dict=None, fetch=False, **kwargs):
"""Construct an instance of the WikiPage object."""
if not subreddit and not page:
subreddit = json_dict['sr']
page = json_dict['page']
info_url = reddit_session.config['wiki_page'] % (
six.text_type(subreddit), page)
super(WikiPage, self).__init__(reddit_session, json_dict, fetch,
info_url)
info_url, **kwargs)
self.page = page
self.subreddit = subreddit

Expand Down Expand Up @@ -1779,7 +1792,6 @@ def edit_settings(self, permlevel, listed, *args, **kwargs):
return self.reddit_session.request_json(url, data=data, *args,
**kwargs)['data']

@restrict_access(scope='modwiki')
def remove_editor(self, username, *args, **kwargs):
"""Remove an editor from this wiki page.
Expand Down
Loading

0 comments on commit 5b72e72

Please sign in to comment.