Skip to content

Commit

Permalink
Merge pull request #149 from praw-dev/upgrade_setup
Browse files Browse the repository at this point in the history
  • Loading branch information
LilSpazJoekp committed Sep 25, 2023
2 parents 028131b + 17022f8 commit 2f9da80
Show file tree
Hide file tree
Showing 20 changed files with 493 additions and 228 deletions.
2 changes: 1 addition & 1 deletion .github/ISSUE_TEMPLATE/bug_report.yml
Expand Up @@ -28,7 +28,7 @@ body:
- id: credential-check
attributes:
label: |
The `Reddit()` initialization in my code example does not include the following parameters to prevent credential leakage:
The `Reddit()` initialization in my code example does not include the following parameters to prevent credential leakage:
`client_secret`, `password`, or `refresh_token`.
options:
- label: "Yes"
Expand Down
57 changes: 30 additions & 27 deletions .pre-commit-config.yaml
@@ -1,10 +1,36 @@
default_language_version:
python: python3.11
repos:

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
hooks:
- id: check-added-large-files
- id: check-executables-have-shebangs
- id: check-shebang-scripts-are-executable
- id: check-toml
- id: check-yaml
- id: end-of-file-fixer
exclude: .*\.txt
rev: v4.4.0
- id: mixed-line-ending
args: [ --fix=no ]
- id: name-tests-test
args: [ --pytest-test-first ]
files: ^tests/integration/.*\.py|tests/unit/.*\.py$
- id: sort-simple-yaml
files: ^(\.github/workflows/.*\.ya?ml|\.readthedocs.ya?ml)$
- id: trailing-whitespace

- repo: https://github.com/pappasam/toml-sort
rev: v0.23.1
hooks:
- id: toml-sort-fix
files: ^(.*\.toml)$

- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.0.290
hooks:
- id: ruff
args: [ --exit-non-zero-on-fix, --fix ]
files: ^(prawcore/.*.py)$

- repo: https://github.com/psf/black
hooks:
Expand All @@ -14,28 +40,5 @@ repos:
- repo: https://github.com/LilSpazJoekp/docstrfmt
hooks:
- id: docstrfmt
require_serial: true
rev: v1.5.1

- repo: https://github.com/pycqa/flake8
hooks:
- id: flake8
rev: 6.1.0

- repo: https://github.com/ikamensh/flynt/
hooks:
- id: flynt
args:
- '-ll'
- '1000'
rev: '1.0.1'

- repo: https://github.com/pycqa/isort
hooks:
- id: isort
rev: 5.12.0

- repo: https://github.com/pycqa/pydocstyle
hooks:
- id: pydocstyle
files: prawcore/.*
rev: 6.3.0
Empty file modified examples/caching_requestor.py 100644 → 100755
Empty file.
12 changes: 6 additions & 6 deletions prawcore/__init__.py
@@ -1,8 +1,8 @@
"""prawcore: Low-level communication layer for PRAW 4+."""
""""Low-level communication layer for PRAW 4+."""

import logging

from .auth import ( # noqa
from .auth import (
Authorizer,
DeviceIDAuthorizer,
ImplicitAuthorizer,
Expand All @@ -11,9 +11,9 @@
TrustedAuthenticator,
UntrustedAuthenticator,
)
from .const import __version__ # noqa
from .exceptions import * # noqa
from .requestor import Requestor # noqa
from .sessions import Session, session # noqa
from .const import __version__
from .exceptions import * # noqa: F403
from .requestor import Requestor
from .sessions import Session, session

logging.getLogger(__package__).addHandler(logging.NullHandler())
87 changes: 47 additions & 40 deletions prawcore/auth.py
@@ -1,7 +1,9 @@
"""Provides Authentication and Authorization classes."""
from __future__ import annotations

import time
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Callable, List, Optional, Set, Tuple, Type, Union
from typing import TYPE_CHECKING, Any, Callable

from requests import Request
from requests.status_codes import codes
Expand All @@ -24,9 +26,9 @@ def _auth(self):

def __init__(
self,
requestor: "Requestor",
requestor: Requestor,
client_id: str,
redirect_uri: Optional[str] = None,
redirect_uri: str | None = None,
) -> None:
"""Represent a single authentication to Reddit's API.
Expand All @@ -43,7 +45,9 @@ def __init__(
self.client_id = client_id
self.redirect_uri = redirect_uri

def _post(self, url: str, success_status: int = codes["ok"], **data) -> "Response":
def _post(
self, url: str, success_status: int = codes["ok"], **data: Any
) -> Response:
response = self._requestor.request(
"post",
url,
Expand All @@ -58,7 +62,7 @@ def _post(self, url: str, success_status: int = codes["ok"], **data) -> "Respons
def authorize_url(
self,
duration: str,
scopes: List[str],
scopes: list[str],
state: str,
implicit: bool = False,
) -> str:
Expand Down Expand Up @@ -86,16 +90,16 @@ def authorize_url(
"""
if self.redirect_uri is None:
raise InvalidInvocation("redirect URI not provided")
msg = "redirect URI not provided"
raise InvalidInvocation(msg)
if implicit and not isinstance(self, UntrustedAuthenticator):
raise InvalidInvocation(
"Only UntrustedAuthenticator instances can "
"use the implicit grant flow."
msg = (
"Only UntrustedAuthenticator instances can use the implicit grant flow."
)
raise InvalidInvocation(msg)
if implicit and duration != "temporary":
raise InvalidInvocation(
"The implicit grant flow only supports temporary access tokens."
)
msg = "The implicit grant flow only supports temporary access tokens."
raise InvalidInvocation(msg)

params = {
"client_id": self.client_id,
Expand All @@ -107,9 +111,9 @@ def authorize_url(
}
url = self._requestor.reddit_url + const.AUTHORIZATION_PATH
request = Request("GET", url, params=params)
return request.prepare().url # type: ignore
return request.prepare().url

def revoke_token(self, token: str, token_type: Optional[str] = None) -> None:
def revoke_token(self, token: str, token_type: str | None = None) -> None:
"""Ask Reddit to revoke the provided token.
:param token: The access or refresh token to revoke.
Expand All @@ -128,7 +132,7 @@ def revoke_token(self, token: str, token_type: Optional[str] = None) -> None:
class BaseAuthorizer(ABC):
"""Superclass for OAuth2 authorization tokens and scopes."""

AUTHENTICATOR_CLASS: Union[Tuple, Type] = BaseAuthenticator
AUTHENTICATOR_CLASS: tuple | type = BaseAuthenticator

def __init__(self, authenticator: BaseAuthenticator) -> None:
"""Represent a single authorization to Reddit's API.
Expand All @@ -142,10 +146,10 @@ def __init__(self, authenticator: BaseAuthenticator) -> None:

def _clear_access_token(self) -> None:
self._expiration_timestamp: float
self.access_token: Optional[str] = None
self.scopes: Optional[Set[str]] = None
self.access_token: str | None = None
self.scopes: set[str] | None = None

def _request_token(self, **data) -> None:
def _request_token(self, **data: Any) -> None:
url = self._authenticator._requestor.reddit_url + const.ACCESS_TOKEN_PATH
pre_request_time = time.time()
response = self._authenticator._post(url=url, **data)
Expand Down Expand Up @@ -186,7 +190,8 @@ def is_valid(self) -> bool:
def revoke(self) -> None:
"""Revoke the current Authorization."""
if self.access_token is None:
raise InvalidInvocation("no token available to revoke")
msg = "no token available to revoke"
raise InvalidInvocation(msg)

self._authenticator.revoke_token(self.access_token, "access_token")
self._clear_access_token()
Expand All @@ -199,10 +204,10 @@ class TrustedAuthenticator(BaseAuthenticator):

def __init__(
self,
requestor: "Requestor",
requestor: Requestor,
client_id: str,
client_secret: str,
redirect_uri: Optional[str] = None,
redirect_uri: str | None = None,
) -> None:
"""Represent a single authentication to Reddit's API.
Expand All @@ -216,17 +221,17 @@ def __init__(
(default: ``None``).
"""
super(TrustedAuthenticator, self).__init__(requestor, client_id, redirect_uri)
super().__init__(requestor, client_id, redirect_uri)
self.client_secret = client_secret

def _auth(self) -> Tuple[str, str]:
def _auth(self) -> tuple[str, str]:
return self.client_id, self.client_secret


class UntrustedAuthenticator(BaseAuthenticator):
"""Store OAuth2 authentication credentials for installed applications."""

def _auth(self) -> Tuple[str, str]:
def _auth(self) -> tuple[str, str]:
return self.client_id, ""


Expand All @@ -237,9 +242,9 @@ def __init__(
self,
authenticator: BaseAuthenticator,
*,
post_refresh_callback: Optional[Callable[["Authorizer"], None]] = None,
pre_refresh_callback: Optional[Callable[["Authorizer"], None]] = None,
refresh_token: Optional[str] = None,
post_refresh_callback: Callable[[Authorizer], None] | None = None,
pre_refresh_callback: Callable[[Authorizer], None] | None = None,
refresh_token: str | None = None,
) -> None:
"""Represent a single authorization to Reddit's API.
Expand All @@ -257,7 +262,7 @@ def __init__(
:param refresh_token: Enables the ability to refresh the authorization.
"""
super(Authorizer, self).__init__(authenticator)
super().__init__(authenticator)
self._post_refresh_callback = post_refresh_callback
self._pre_refresh_callback = pre_refresh_callback
self.refresh_token = refresh_token
Expand All @@ -270,7 +275,8 @@ def authorize(self, code: str) -> None:
"""
if self._authenticator.redirect_uri is None:
raise InvalidInvocation("redirect URI not provided")
msg = "redirect URI not provided"
raise InvalidInvocation(msg)
self._request_token(
code=code,
grant_type="authorization_code",
Expand All @@ -282,7 +288,8 @@ def refresh(self) -> None:
if self._pre_refresh_callback:
self._pre_refresh_callback(self)
if self.refresh_token is None:
raise InvalidInvocation("refresh token not provided")
msg = "refresh token not provided"
raise InvalidInvocation(msg)
self._request_token(
grant_type="refresh_token", refresh_token=self.refresh_token
)
Expand All @@ -300,7 +307,7 @@ def revoke(self, only_access: bool = False) -> None:
"""
if only_access or self.refresh_token is None:
super(Authorizer, self).revoke()
super().revoke()
else:
self._authenticator.revoke_token(self.refresh_token, "refresh_token")
self._clear_access_token()
Expand Down Expand Up @@ -333,7 +340,7 @@ def __init__(
from Reddit in the callback to the authenticator's redirect uri.
"""
super(ImplicitAuthorizer, self).__init__(authenticator)
super().__init__(authenticator)
self._expiration_timestamp = time.time() + expires_in
self.access_token = access_token
self.scopes = set(scope.split(" "))
Expand All @@ -352,7 +359,7 @@ class ReadOnlyAuthorizer(Authorizer):
def __init__(
self,
authenticator: BaseAuthenticator,
scopes: Optional[List[str]] = None,
scopes: list[str] | None = None,
) -> None:
"""Represent a ReadOnly authorization to Reddit's API.
Expand Down Expand Up @@ -384,10 +391,10 @@ class ScriptAuthorizer(Authorizer):
def __init__(
self,
authenticator: BaseAuthenticator,
username: Optional[str],
password: Optional[str],
two_factor_callback: Optional[Callable] = None,
scopes: Optional[List[str]] = None,
username: str | None,
password: str | None,
two_factor_callback: Callable | None = None,
scopes: list[str] | None = None,
) -> None:
"""Represent a single personal-use authorization to Reddit's API.
Expand All @@ -401,7 +408,7 @@ def __init__(
``None``). The scope ``"*"`` is requested when the default argument is used.
"""
super(ScriptAuthorizer, self).__init__(authenticator)
super().__init__(authenticator)
self._password = password
self._scopes = scopes
self._two_factor_callback = two_factor_callback
Expand Down Expand Up @@ -436,8 +443,8 @@ class DeviceIDAuthorizer(BaseAuthorizer):
def __init__(
self,
authenticator: BaseAuthenticator,
device_id: Optional[str] = None,
scopes: Optional[List[str]] = None,
device_id: str | None = None,
scopes: list[str] | None = None,
) -> None:
"""Represent an app-only OAuth2 authorization for 'installed' apps.
Expand Down
12 changes: 8 additions & 4 deletions prawcore/const.py
Expand Up @@ -3,8 +3,12 @@

__version__ = "2.3.0"

ACCESS_TOKEN_PATH = "/api/v1/access_token"
AUTHORIZATION_PATH = "/api/v1/authorize"
REVOKE_TOKEN_PATH = "/api/v1/revoke_token"
TIMEOUT = float(os.environ.get("prawcore_timeout", 16))
ACCESS_TOKEN_PATH = "/api/v1/access_token" # noqa: S105
AUTHORIZATION_PATH = "/api/v1/authorize" # noqa: S105
REVOKE_TOKEN_PATH = "/api/v1/revoke_token" # noqa: S105
TIMEOUT = float(
os.environ.get(
"PRAWCORE_TIMEOUT", os.environ.get("prawcore_timeout", 16) # noqa: SIM112
)
)
WINDOW_SIZE = 600

0 comments on commit 2f9da80

Please sign in to comment.