diff --git a/prawcore/__init__.py b/prawcore/__init__.py index 9cc2f31..94d8593 100644 --- a/prawcore/__init__.py +++ b/prawcore/__init__.py @@ -1,7 +1,8 @@ """prawcore: Low-level communication layer for PRAW 4+.""" import logging -from .auth import Authenticator, Authorizer, ReadOnlyAuthorizer # noqa +from .auth import (Authenticator, Authorizer, ReadOnlyAuthorizer, # noqa + ScriptAuthorizer) from .const import __version__ # noqa from .exceptions import * # noqa from .sessions import Session, session # noqa diff --git a/prawcore/auth.py b/prawcore/auth.py index be16328..4366a49 100644 --- a/prawcore/auth.py +++ b/prawcore/auth.py @@ -1,7 +1,7 @@ """Provides Authentication and Authorization classes.""" import time from . import const, util -from .exceptions import InvalidInvocation, RequestException +from .exceptions import InvalidInvocation, OAuthException, RequestException from requests.status_codes import codes @@ -46,6 +46,10 @@ def _request_token(self, **data): payload = response.json() + if 'error' in payload: # Why are these OKAY responses? + raise OAuthException(response, payload['error'], + payload.get('error_description')) + self._expiration_timestamp = time.time() + payload['expires_in'] self.access_token = payload['access_token'] self.scopes = set(payload['scope'].split(' ')) @@ -79,3 +83,30 @@ class ReadOnlyAuthorizer(Authorizer): def refresh(self): """Obtain a new ReadOnly access token.""" self._request_token(grant_type='client_credentials') + + +class ScriptAuthorizer(Authorizer): + """Manages personal-use script type authorizations. + + Only users who are listed as developers for the application will be + granted access tokens. + + """ + + def __init__(self, authenticator, username, password): + """Represent a single personal-use authorization to reddit's API. + + :param authenticator: An instance of :class:`Authenticator`. + :param username: The reddit username of one of the application's + developers. + :param password: The password associated with ``username``. + + """ + super(ScriptAuthorizer, self).__init__(authenticator) + self._username = username + self._password = password + + def refresh(self): + """Obtain a new personal-use script type access token.""" + self._request_token(grant_type='password', username=self._username, + password=self._password) diff --git a/prawcore/exceptions.py b/prawcore/exceptions.py index 17784ba..33677c9 100644 --- a/prawcore/exceptions.py +++ b/prawcore/exceptions.py @@ -23,6 +23,24 @@ def __init__(self, response): .format(response.status_code)) +class OAuthException(PrawcoreException): + """Indicate that there was an OAuth2 related error with the request.""" + + def __init__(self, response, error, description): + """OAuthException instances contain the failing response. + + :param response: A requests.response instance. + :param error: The error type returned by reddit. + :param description: A description of the error when provided. + + """ + self.error = error + self.description = description + self.response = response + PrawcoreException.__init__(self, '{} error processing request ({})' + .format(error, description)) + + class InsufficientScope(RequestException): """Indicate that the request requires a different scope."""