Skip to content

Commit

Permalink
Merge bb0e000 into 1db01c5
Browse files Browse the repository at this point in the history
  • Loading branch information
bboe committed Feb 4, 2021
2 parents 1db01c5 + bb0e000 commit 304819e
Show file tree
Hide file tree
Showing 9 changed files with 121 additions and 28 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Expand Up @@ -60,6 +60,6 @@ jobs:
run: coveralls
strategy:
matrix:
python-version: [3.6, 3.7, 3.8]
python-version: [3.6, 3.7, 3.8, 3.9]
name: CI
on: [pull_request, push]
11 changes: 5 additions & 6 deletions .pre-commit-config.yaml
@@ -1,8 +1,7 @@
default_language_version:
python: python3.7
fail_fast: true
repos:
- hooks:
- id: black
repo: https://github.com/ambv/black
rev: stable
- hooks:
- id: black
language_version: python3
repo: https://github.com/psf/black
rev: stable
32 changes: 23 additions & 9 deletions CHANGES.rst
Expand Up @@ -4,6 +4,23 @@ Change Log
prawcore follows `semantic versioning <http://semver.org/>`_ with the exception
that deprecations will not be announced by a minor release.

Unreleased
----------

**Added**

* ``Authorizer`` optionally takes a ``pre_refresh_callback`` keyword
argument. If provided, the function will called with the instance of
``Authorizer`` prior to refreshing the access and refresh tokens.
* ``Authorizer`` optionally takes a ``post_refresh_callback`` keyword
argument. If provided, the function will called with the instance of
``Authorizer`` after refreshing the access and refresh tokens.

**Changed**

* The ``refresh_token`` argument to ``Authorizer`` must now be passed by
keyword, and cannot be passed as a positional argument.

1.5.0 (2020-08-04)
------------------

Expand All @@ -16,8 +33,8 @@ that deprecations will not be announced by a minor release.

**Added**

* When calling :meth:`.Session.request`, we add the key-value pair
``"api_type": "json"`` to the ``json`` parameter, if it is a ``dict``.
* When calling ``Session.request``, we add the key-value pair ``"api_type":
"json"`` to the ``json`` parameter, if it is a ``dict``.

**Changed**

Expand All @@ -31,25 +48,22 @@ that deprecations will not be announced by a minor release.

**Added**

* All other requestor methods, most notably :meth:`.Session.request`, now contain
a ``timeout`` parameter.
* All other requestor methods, most notably ``Session.request``, now contain a
``timeout`` parameter.


1.2.0 (2020-04-23)
------------------

**Added**

* Method ``Requestor.request`` can be given a timeout parameter to
control the amount of time to wait for a request to succeed.
* Method ``Requestor.request`` can be given a timeout parameter to control the
amount of time to wait for a request to succeed.

**Changed**

* Updated rate limit algorithm to more intelligently rate limit when there
are extra requests remaining.

**Removed**

* Drop python 2.7 support.

1.0.1 (2019-02-05)
Expand Down
2 changes: 1 addition & 1 deletion examples/obtain_refresh_token.py
Expand Up @@ -48,7 +48,7 @@ def main():
prawcore.Requestor("prawcore_refresh_token_example"),
os.environ["PRAWCORE_CLIENT_ID"],
os.environ["PRAWCORE_CLIENT_SECRET"],
os.environ["PRAWCORE_REDIRECT_URI"],
"http://localhost:8080",
)

state = str(random.randint(0, 65000))
Expand Down
25 changes: 24 additions & 1 deletion prawcore/auth.py
Expand Up @@ -199,16 +199,35 @@ class Authorizer(BaseAuthorizer):

AUTHENTICATOR_CLASS = BaseAuthenticator

def __init__(self, authenticator, refresh_token=None):
def __init__(
self,
authenticator,
*,
post_refresh_callback=None,
pre_refresh_callback=None,
refresh_token=None
):
"""Represent a single authorization to Reddit's API.
:param authenticator: An instance of a subclass of
:class:`BaseAuthenticator`.
:param post_refresh_callback: (Optional) When a single-argument
function is passed, the function will be called prior to refreshing
the access and refresh tokens. The argument to the callback is the
:class:`Authorizor` instance. This callback can be used to inspect
and modify the attributes of the :class:`Authorizor`.
:param pre_refresh_callback: (Optional) When a single-argument function
is passed, the function will be called after refreshing the access
and refresh tokens. The argument to the callback is the
:class:`Authorizor` instance. This callback can be used to inspect
and modify the attributes of the :class:`Authorizor`.
:param refresh_token: (Optional) Enables the ability to refresh the
authorization.
"""
super(Authorizer, self).__init__(authenticator)
self._post_refresh_callback = post_refresh_callback
self._pre_refresh_callback = pre_refresh_callback
self.refresh_token = refresh_token

def authorize(self, code):
Expand All @@ -228,11 +247,15 @@ def authorize(self, code):

def refresh(self):
"""Obtain a new access token from the refresh_token."""
if self._pre_refresh_callback:
self._pre_refresh_callback(self)
if self.refresh_token is None:
raise InvalidInvocation("refresh token not provided")
self._request_token(
grant_type="refresh_token", refresh_token=self.refresh_token
)
if self._post_refresh_callback:
self._post_refresh_callback(self)

def revoke(self, only_access=False):
"""Revoke the current Authorization.
Expand Down
7 changes: 5 additions & 2 deletions prawcore/sessions.py
Expand Up @@ -202,8 +202,11 @@ def _make_request(
)
return response, None
except RequestException as exception:
if not retry_strategy_state.should_retry_on_failure() or not isinstance( # noqa: E501
exception.original_exception, self.RETRY_EXCEPTIONS
if (
not retry_strategy_state.should_retry_on_failure()
or not isinstance( # noqa: E501
exception.original_exception, self.RETRY_EXCEPTIONS
)
):
raise
return None, exception.original_exception
Expand Down
1 change: 1 addition & 0 deletions setup.py
Expand Up @@ -42,6 +42,7 @@
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
],
description="Low-level communication layer for PRAW 4+.",
extras_require=extras,
Expand Down
65 changes: 58 additions & 7 deletions tests/test_authorizer.py
Expand Up @@ -77,7 +77,9 @@ def test_initialize(self):
self.assertFalse(authorizer.is_valid())

def test_initialize__with_refresh_token(self):
authorizer = prawcore.Authorizer(self.authentication, REFRESH_TOKEN)
authorizer = prawcore.Authorizer(
self.authentication, refresh_token=REFRESH_TOKEN
)
self.assertIsNone(authorizer.access_token)
self.assertIsNone(authorizer.scopes)
self.assertEqual(REFRESH_TOKEN, authorizer.refresh_token)
Expand All @@ -92,7 +94,46 @@ def test_initialize__with_untrusted_authenticator(self):
self.assertFalse(authorizer.is_valid())

def test_refresh(self):
authorizer = prawcore.Authorizer(self.authentication, REFRESH_TOKEN)
authorizer = prawcore.Authorizer(
self.authentication, refresh_token=REFRESH_TOKEN
)
with Betamax(REQUESTOR).use_cassette("Authorizer_refresh"):
authorizer.refresh()

self.assertIsNotNone(authorizer.access_token)
self.assertIsInstance(authorizer.scopes, set)
self.assertTrue(len(authorizer.scopes) > 0)
self.assertTrue(authorizer.is_valid())

def test_refresh__post_refresh_callback(self):
def callback(authorizer):
# Reddit isn't currently returning new refresh_tokens so
# the following assertion is currently commented out.
# self.assertNotEqual(REFRESH_TOKEN, authorizer.refresh_token)
authorizer.refresh_token = "manually_updated"

authorizer = prawcore.Authorizer(
self.authentication,
post_refresh_callback=callback,
refresh_token=REFRESH_TOKEN,
)
with Betamax(REQUESTOR).use_cassette("Authorizer_refresh"):
authorizer.refresh()

self.assertIsNotNone(authorizer.access_token)
self.assertEqual("manually_updated", authorizer.refresh_token)
self.assertIsInstance(authorizer.scopes, set)
self.assertTrue(len(authorizer.scopes) > 0)
self.assertTrue(authorizer.is_valid())

def test_refresh__pre_refresh_callback(self):
def callback(authorizer):
self.assertIsNone(authorizer.refresh_token)
authorizer.refresh_token = REFRESH_TOKEN

authorizer = prawcore.Authorizer(
self.authentication, pre_refresh_callback=callback
)
with Betamax(REQUESTOR).use_cassette("Authorizer_refresh"):
authorizer.refresh()

Expand All @@ -102,7 +143,9 @@ def test_refresh(self):
self.assertTrue(authorizer.is_valid())

def test_refresh__with_invalid_token(self):
authorizer = prawcore.Authorizer(self.authentication, "INVALID_TOKEN")
authorizer = prawcore.Authorizer(
self.authentication, refresh_token="INVALID_TOKEN"
)
with Betamax(REQUESTOR).use_cassette(
"Authorizer_refresh__with_invalid_token"
):
Expand All @@ -115,7 +158,9 @@ def test_refresh__without_refresh_token(self):
self.assertFalse(authorizer.is_valid())

def test_revoke__access_token_with_refresh_set(self):
authorizer = prawcore.Authorizer(self.authentication, REFRESH_TOKEN)
authorizer = prawcore.Authorizer(
self.authentication, refresh_token=REFRESH_TOKEN
)
with Betamax(REQUESTOR).use_cassette(
"Authorizer_revoke__access_token_with_refresh_set"
):
Expand Down Expand Up @@ -146,7 +191,9 @@ def test_revoke__access_token_without_refresh_set(self):
self.assertFalse(authorizer.is_valid())

def test_revoke__refresh_token_with_access_set(self):
authorizer = prawcore.Authorizer(self.authentication, REFRESH_TOKEN)
authorizer = prawcore.Authorizer(
self.authentication, refresh_token=REFRESH_TOKEN
)
with Betamax(REQUESTOR).use_cassette(
"Authorizer_revoke__refresh_token_with_access_set"
):
Expand All @@ -159,7 +206,9 @@ def test_revoke__refresh_token_with_access_set(self):
self.assertFalse(authorizer.is_valid())

def test_revoke__refresh_token_without_access_set(self):
authorizer = prawcore.Authorizer(self.authentication, REFRESH_TOKEN)
authorizer = prawcore.Authorizer(
self.authentication, refresh_token=REFRESH_TOKEN
)
with Betamax(REQUESTOR).use_cassette(
"Authorizer_revoke__refresh_token_without_access_set"
):
Expand All @@ -171,7 +220,9 @@ def test_revoke__refresh_token_without_access_set(self):
self.assertFalse(authorizer.is_valid())

def test_revoke__without_access_token(self):
authorizer = prawcore.Authorizer(self.authentication, REFRESH_TOKEN)
authorizer = prawcore.Authorizer(
self.authentication, refresh_token=REFRESH_TOKEN
)
self.assertRaises(
prawcore.InvalidInvocation, authorizer.revoke, only_access=True
)
Expand Down
4 changes: 3 additions & 1 deletion tests/test_sessions.py
Expand Up @@ -38,7 +38,9 @@ def client_authorizer():
authenticator = prawcore.TrustedAuthenticator(
REQUESTOR, CLIENT_ID, CLIENT_SECRET
)
authorizer = prawcore.Authorizer(authenticator, REFRESH_TOKEN)
authorizer = prawcore.Authorizer(
authenticator, refresh_token=REFRESH_TOKEN
)
authorizer.refresh()
return authorizer

Expand Down

0 comments on commit 304819e

Please sign in to comment.