Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Merge 83aa52c into 496cfb8
  • Loading branch information
LilSpazJoekp committed Mar 7, 2022
2 parents 496cfb8 + 83aa52c commit f4a85be
Show file tree
Hide file tree
Showing 9 changed files with 49 additions and 28 deletions.
5 changes: 5 additions & 0 deletions .coveragerc
@@ -0,0 +1,5 @@
[report]
exclude_lines =
@abstract
if TYPE_CHECKING:
pragma: no cover
4 changes: 2 additions & 2 deletions examples/caching_requestor.py
Expand Up @@ -2,7 +2,7 @@

"""This example shows how simple in-memory caching can be used.
Demonstrates the use of custom sessions with :class:`Requestor`. It's an adaptation of
Demonstrates the use of custom sessions with :class:`.Requestor`. It's an adaptation of
``read_only_auth_trophies.py``.
"""
Expand All @@ -19,7 +19,7 @@ class CachingSession(requests.Session):
"""Cache GETs in memory.
Toy example of custom session to showcase the ``session`` parameter of
:class:`Requestor`.
:class:`.Requestor`.
"""

Expand Down
13 changes: 9 additions & 4 deletions prawcore/auth.py
@@ -1,5 +1,6 @@
"""Provides Authentication and Authorization classes."""
import time
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Callable, List, Optional, Set, Tuple, Type, Union

from requests import Request
Expand All @@ -8,13 +9,13 @@
from . import const
from .exceptions import InvalidInvocation, OAuthException, ResponseException

if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from requests.models import Response

from prawcore.requestor import Requestor


class BaseAuthenticator(object):
class BaseAuthenticator(ABC):
"""Provide the base authenticator object that stores OAuth2 credentials."""

def __init__(
Expand All @@ -38,6 +39,10 @@ def __init__(
self.client_id = client_id
self.redirect_uri = redirect_uri

@abstractmethod
def _auth(self):
pass

def _post(self, url: str, success_status: int = codes["ok"], **data) -> "Response":
response = self._requestor.request(
"post",
Expand Down Expand Up @@ -123,7 +128,7 @@ def revoke_token(self, token: str, token_type: Optional[str] = None) -> None:
class TrustedAuthenticator(BaseAuthenticator):
"""Store OAuth2 authentication credentials for web, or script type apps."""

RESPONSE_TYPE = "code"
RESPONSE_TYPE: str = "code"

def __init__(
self,
Expand Down Expand Up @@ -158,7 +163,7 @@ def _auth(self) -> Tuple[str, str]:
return self.client_id, ""


class BaseAuthorizer(object):
class BaseAuthorizer(ABC):
"""Superclass for OAuth2 authorization tokens and scopes."""

AUTHENTICATOR_CLASS: Union[Tuple, Type] = BaseAuthenticator
Expand Down
2 changes: 1 addition & 1 deletion prawcore/exceptions.py
Expand Up @@ -2,7 +2,7 @@
from typing import TYPE_CHECKING, Any, Dict, Optional, Tuple, Union
from urllib.parse import urlparse

if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from requests.models import Response


Expand Down
18 changes: 10 additions & 8 deletions prawcore/rate_limit.py
@@ -1,10 +1,10 @@
"""Provide the RateLimiter class."""
import logging
import time
from typing import Callable, Dict, Optional, Union
from typing import TYPE_CHECKING, Any, Callable, Dict, Mapping, Optional

from requests.models import Response
from requests.structures import CaseInsensitiveDict
if TYPE_CHECKING:
from requests.models import Response

log = logging.getLogger(__package__)

Expand All @@ -24,8 +24,12 @@ def __init__(self) -> None:
self.used: Optional[int] = None

def call(
self, request_function: Callable, set_header_callback: Callable, *args, **kwargs
) -> Response:
self,
request_function: Callable[[Any], "Response"],
set_header_callback: Callable[[], Dict[str, str]],
*args,
**kwargs,
) -> "Response":
"""Rate limit the call to ``request_function``.
:param request_function: A function call that returns an HTTP response object.
Expand All @@ -52,9 +56,7 @@ def delay(self) -> None:
log.debug(message)
time.sleep(sleep_seconds)

def update(
self, response_headers: Union[Dict[str, str], CaseInsensitiveDict]
) -> None:
def update(self, response_headers: Mapping[str, str]) -> None:
"""Update the state of the rate limiter based on the response headers.
This method should only be called following an HTTP request to Reddit.
Expand Down
4 changes: 2 additions & 2 deletions prawcore/requestor.py
Expand Up @@ -6,7 +6,7 @@
from .const import TIMEOUT, __version__
from .exceptions import InvalidInvocation, RequestException

if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from requests.models import Response

from .sessions import Session
Expand All @@ -16,7 +16,7 @@ class Requestor(object):
"""Requestor provides an interface to HTTP requests."""

def __getattr__(self, attribute: str) -> Any:
"""Pass all undefined attributes to the _http attribute."""
"""Pass all undefined attributes to the ``_http`` attribute."""
if attribute.startswith("__"):
raise AttributeError
return getattr(self._http, attribute)
Expand Down
18 changes: 12 additions & 6 deletions prawcore/sessions.py
Expand Up @@ -2,6 +2,7 @@
import logging
import random
import time
from abc import ABC, abstractmethod
from copy import deepcopy
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple, Union
from urllib.parse import urljoin
Expand Down Expand Up @@ -29,7 +30,7 @@
from .rate_limit import RateLimiter
from .util import authorization_error_class

if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from io import BufferedReader

from requests.models import Response
Expand All @@ -40,7 +41,7 @@
log = logging.getLogger(__package__)


class RetryStrategy(object):
class RetryStrategy(ABC):
"""An abstract class for scheduling request retries.
The strategy controls both the number and frequency of retry attempts.
Expand All @@ -49,6 +50,10 @@ class RetryStrategy(object):
"""

@abstractmethod
def _sleep_seconds(self) -> Optional[float]:
pass

def sleep(self) -> None:
"""Sleep until we are ready to attempt the request."""
sleep_seconds = self._sleep_seconds()
Expand Down Expand Up @@ -115,7 +120,7 @@ class Session(object):
codes["unauthorized"]: authorization_error_class,
codes[
"unavailable_for_legal_reasons"
]: UnavailableForLegalReasons, # Cloudflare status (not named in requests)
]: UnavailableForLegalReasons, # Cloudflare's status (not named in requests)
520: ServerError,
522: ServerError,
}
Expand Down Expand Up @@ -167,7 +172,7 @@ def _do_retry(
saved_exception: Optional[Exception],
timeout: float,
url: str,
) -> Optional[Union[Dict[Any, Any], str]]:
) -> Optional[Union[Dict[str, Any], str]]:
if saved_exception:
status = repr(saved_exception)
else:
Expand Down Expand Up @@ -234,7 +239,7 @@ def _request_with_retries(
timeout: float,
url: str,
retry_strategy_state: Optional["FiniteRetryStrategy"] = None,
) -> Optional[Union[Dict[Any, Any], str]]:
) -> Optional[Union[Dict[str, Any], str]]:
if retry_strategy_state is None:
retry_strategy_state = self._retry_strategy_class()

Expand Down Expand Up @@ -308,7 +313,7 @@ def request(
json: Optional[Dict[str, Any]] = None,
params: Optional[Dict[str, Any]] = None,
timeout: float = TIMEOUT,
) -> Optional[Union[Dict[Any, Any], str]]:
) -> Optional[Union[Dict[str, Any], str]]:
"""Return the json content from the resource at ``path``.
:param method: The request verb. E.g., ``"GET"``, ``"POST"``, ``"PUT"``.
Expand All @@ -319,6 +324,7 @@ def request(
:param files: Dictionary, mapping ``filename`` to file-like object.
:param json: Object to be serialized to JSON in the body of the request.
:param params: The query parameters to send with the request.
:param timeout: Specifies a particular timeout, in seconds.
Automatically refreshes the access token if it becomes invalid and a refresh
token is available.
Expand Down
2 changes: 1 addition & 1 deletion prawcore/util.py
Expand Up @@ -3,7 +3,7 @@

from .exceptions import Forbidden, InsufficientScope, InvalidToken

if TYPE_CHECKING: # pragma: no cover
if TYPE_CHECKING:
from requests.models import Response

_auth_error_mapping = {
Expand Down
11 changes: 7 additions & 4 deletions tests/unit/test_authorizer.py
Expand Up @@ -16,6 +16,11 @@ def setup(self):
)


class InvalidAuthenticator(prawcore.auth.BaseAuthenticator):
def _auth(self):
pass


class TestAuthorizer(AuthorizerBase):
def test_authorize__fail_without_redirect_uri(self):
authorizer = prawcore.Authorizer(self.authentication)
Expand Down Expand Up @@ -79,10 +84,8 @@ def test_initialize(self):
assert authorizer.scopes is None
assert not authorizer.is_valid()

def test_initialize__with_base_authenticator(self):
authenticator = prawcore.Authorizer(
prawcore.auth.BaseAuthenticator(None, None, None)
)
def test_initialize__with_invalid_authenticator(self):
authenticator = prawcore.Authorizer(InvalidAuthenticator(None, None, None))
with pytest.raises(prawcore.InvalidInvocation):
prawcore.DeviceIDAuthorizer(
authenticator,
Expand Down

0 comments on commit f4a85be

Please sign in to comment.