Initial commit
This commit is contained in:
5
backend/venv/Lib/site-packages/praw/util/__init__.py
Normal file
5
backend/venv/Lib/site-packages/praw/util/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""Package imports for utilities."""
|
||||
|
||||
from .cache import cachedproperty
|
||||
from .deprecate_args import _deprecate_args
|
||||
from .snake import camel_to_snake, snake_case_keys
|
||||
52
backend/venv/Lib/site-packages/praw/util/cache.py
Normal file
52
backend/venv/Lib/site-packages/praw/util/cache.py
Normal file
@@ -0,0 +1,52 @@
|
||||
"""Caching utilities."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any, Callable
|
||||
|
||||
|
||||
class cachedproperty: # noqa: N801
|
||||
"""A decorator for caching a property's result.
|
||||
|
||||
Similar to :py:class:`property`, but the wrapped method's result is cached on the
|
||||
instance. This is achieved by setting an entry in the object's instance dictionary
|
||||
with the same name as the property. When the name is later accessed, the value in
|
||||
the instance dictionary takes precedence over the (non-data descriptor) property.
|
||||
|
||||
This is useful for implementing lazy-loaded properties.
|
||||
|
||||
The cache can be invalidated via :py:meth:`delattr`, or by modifying ``__dict__``
|
||||
directly. It will be repopulated on next access.
|
||||
|
||||
.. versionadded:: 6.3.0
|
||||
|
||||
"""
|
||||
|
||||
# This to make sphinx run properly
|
||||
def __call__(self, *args: Any, **kwargs: Any): # pragma: no cover
|
||||
"""Empty method to make sphinx run properly."""
|
||||
|
||||
def __get__(self, obj: Any | None, objtype: Any | None = None) -> Any:
|
||||
"""Implement descriptor getter.
|
||||
|
||||
Calculate the property's value and then store it in the associated object's
|
||||
instance dictionary.
|
||||
|
||||
"""
|
||||
if obj is None:
|
||||
return self
|
||||
|
||||
value = obj.__dict__[self.func.__name__] = self.func(obj)
|
||||
return value
|
||||
|
||||
def __init__(self, func: Callable[[Any], Any], doc: str | None = None):
|
||||
"""Initialize a :class:`.cachedproperty` instance."""
|
||||
self.func = self.__wrapped__ = func
|
||||
|
||||
if doc is None:
|
||||
doc = func.__doc__
|
||||
self.__doc__ = doc
|
||||
|
||||
def __repr__(self) -> str:
|
||||
"""Return an object initialization representation of the instance."""
|
||||
return f"<{self.__class__.__name__} {self.func}>"
|
||||
50
backend/venv/Lib/site-packages/praw/util/deprecate_args.py
Normal file
50
backend/venv/Lib/site-packages/praw/util/deprecate_args.py
Normal file
@@ -0,0 +1,50 @@
|
||||
"""Positional argument deprecation decorator."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import inspect
|
||||
from functools import wraps
|
||||
from typing import Any, Callable
|
||||
from warnings import warn
|
||||
|
||||
|
||||
def _deprecate_args(*old_args: str) -> Callable:
|
||||
def _generate_arg_string(used_args: tuple[str, ...]) -> str:
|
||||
used_args = list(map(repr, used_args))
|
||||
arg_count = len(used_args)
|
||||
arg_string = (
|
||||
" and ".join(used_args)
|
||||
if arg_count < 3
|
||||
else f"{', '.join(used_args[:-1])}, and {used_args[-1]}"
|
||||
)
|
||||
arg_string += f" as {'' if arg_count > 1 else 'a '}"
|
||||
arg_string += "keyword argument"
|
||||
return arg_string + ("s" if arg_count > 1 else "")
|
||||
|
||||
def wrapper(func: Callable):
|
||||
@wraps(func)
|
||||
def wrapped(*args: Any, **kwargs: Any):
|
||||
signature = inspect.signature(func)
|
||||
positional_args = [
|
||||
name
|
||||
for name, parameter in signature.parameters.items()
|
||||
if parameter.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD
|
||||
]
|
||||
_old_args = tuple(filter(lambda arg: arg not in positional_args, old_args))
|
||||
if positional_args:
|
||||
# remove the acceptable positional arguments like self or id for helpers
|
||||
kwargs.update(zip(positional_args, args))
|
||||
args = tuple(args[len(positional_args) :])
|
||||
if args:
|
||||
arg_string = _generate_arg_string(_old_args[: len(args)])
|
||||
warn(
|
||||
f"Positional arguments for {func.__qualname__!r} will no longer be"
|
||||
f" supported in PRAW 8.\nCall this function with {arg_string}.",
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
return func(**dict(zip(_old_args, args)), **kwargs)
|
||||
|
||||
return wrapped
|
||||
|
||||
return wrapper
|
||||
22
backend/venv/Lib/site-packages/praw/util/snake.py
Normal file
22
backend/venv/Lib/site-packages/praw/util/snake.py
Normal file
@@ -0,0 +1,22 @@
|
||||
"""Contains functions dealing with snake case conversions."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
_re_camel_to_snake = re.compile(r"([a-z0-9](?=[A-Z])|[A-Z](?=[A-Z][a-z]))")
|
||||
|
||||
|
||||
def camel_to_snake(name: str) -> str:
|
||||
"""Convert ``name`` from camelCase to snake_case."""
|
||||
return _re_camel_to_snake.sub(r"\1_", name).lower()
|
||||
|
||||
|
||||
def snake_case_keys(dictionary: dict[str, Any]) -> dict[str, Any]:
|
||||
"""Return a new dictionary with keys converted to snake_case.
|
||||
|
||||
:param dictionary: The dict to be corrected.
|
||||
|
||||
"""
|
||||
return {camel_to_snake(k): v for k, v in dictionary.items()}
|
||||
203
backend/venv/Lib/site-packages/praw/util/token_manager.py
Normal file
203
backend/venv/Lib/site-packages/praw/util/token_manager.py
Normal file
@@ -0,0 +1,203 @@
|
||||
"""Token Manager classes.
|
||||
|
||||
There should be a 1-to-1 mapping between an instance of a subclass of
|
||||
:class:`.BaseTokenManager` and a :class:`.Reddit` instance.
|
||||
|
||||
A few proof of concept token manager classes are provided here, but it is expected that
|
||||
PRAW users will create their own token manager classes suitable for their needs.
|
||||
|
||||
.. deprecated:: 7.4.0
|
||||
|
||||
Tokens managers have been deprecated and will be removed in the near future.
|
||||
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from pathlib import Path
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from . import _deprecate_args
|
||||
|
||||
if TYPE_CHECKING: # pragma: no cover
|
||||
import prawcore
|
||||
|
||||
import praw
|
||||
|
||||
|
||||
class BaseTokenManager(ABC):
|
||||
"""An abstract class for all token managers."""
|
||||
|
||||
@abstractmethod
|
||||
def post_refresh_callback(self, authorizer: prawcore.auth.BaseAuthorizer):
|
||||
"""Handle callback that is invoked after a refresh token is used.
|
||||
|
||||
:param authorizer: The ``prawcore.Authorizer`` instance used containing
|
||||
``access_token`` and ``refresh_token`` attributes.
|
||||
|
||||
This function will be called after refreshing the access and refresh tokens.
|
||||
This callback can be used for saving the updated ``refresh_token``.
|
||||
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def pre_refresh_callback(self, authorizer: prawcore.auth.BaseAuthorizer):
|
||||
"""Handle callback that is invoked before refreshing PRAW's authorization.
|
||||
|
||||
:param authorizer: The ``prawcore.Authorizer`` instance used containing
|
||||
``access_token`` and ``refresh_token`` attributes.
|
||||
|
||||
This callback can be used to inspect and modify the attributes of the
|
||||
``prawcore.Authorizer`` instance, such as setting the ``refresh_token``.
|
||||
|
||||
"""
|
||||
|
||||
@property
|
||||
def reddit(self) -> praw.Reddit:
|
||||
"""Return the :class:`.Reddit` instance bound to the token manager."""
|
||||
return self._reddit
|
||||
|
||||
@reddit.setter
|
||||
def reddit(self, value: praw.Reddit):
|
||||
if self._reddit is not None:
|
||||
msg = "'reddit' can only be set once and is done automatically"
|
||||
raise RuntimeError(msg)
|
||||
self._reddit = value
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize a :class:`.BaseTokenManager` instance."""
|
||||
self._reddit = None
|
||||
|
||||
|
||||
class FileTokenManager(BaseTokenManager):
|
||||
"""Provides a single-file based token manager.
|
||||
|
||||
It is expected that the file with the initial ``refresh_token`` is created prior to
|
||||
use.
|
||||
|
||||
.. warning::
|
||||
|
||||
The same ``file`` should not be used by more than one instance of this class
|
||||
concurrently. Doing so may result in data corruption. Consider using
|
||||
:class:`.SQLiteTokenManager` if you want more than one instance of PRAW to
|
||||
concurrently manage a specific ``refresh_token`` chain.
|
||||
|
||||
"""
|
||||
|
||||
def __init__(self, filename: str):
|
||||
"""Initialize a :class:`.FileTokenManager` instance.
|
||||
|
||||
:param filename: The file the contains the refresh token.
|
||||
|
||||
"""
|
||||
super().__init__()
|
||||
self._filename = filename
|
||||
|
||||
def post_refresh_callback(self, authorizer: prawcore.auth.BaseAuthorizer):
|
||||
"""Update the saved copy of the refresh token."""
|
||||
with Path(self._filename).open("w") as fp:
|
||||
fp.write(authorizer.refresh_token)
|
||||
|
||||
def pre_refresh_callback(self, authorizer: prawcore.auth.BaseAuthorizer):
|
||||
"""Load the refresh token from the file."""
|
||||
if authorizer.refresh_token is None:
|
||||
with Path(self._filename).open() as fp:
|
||||
authorizer.refresh_token = fp.read().strip()
|
||||
|
||||
|
||||
class SQLiteTokenManager(BaseTokenManager):
|
||||
"""Provides a SQLite3 based token manager.
|
||||
|
||||
Unlike, :class:`.FileTokenManager`, the initial database need not be created ahead
|
||||
of time, as it'll automatically be created on first use. However, initial refresh
|
||||
tokens will need to be registered via :meth:`.register` prior to use.
|
||||
|
||||
.. warning::
|
||||
|
||||
This class is untested on Windows because we encountered file locking issues in
|
||||
the test environment.
|
||||
|
||||
"""
|
||||
|
||||
@_deprecate_args("database", "key")
|
||||
def __init__(self, *, database: str, key: str):
|
||||
"""Initialize a :class:`.SQLiteTokenManager` instance.
|
||||
|
||||
:param database: The path to the SQLite database.
|
||||
:param key: The key used to locate the refresh token. This ``key`` can be
|
||||
anything. You might use the ``client_id`` if you expect to have unique a
|
||||
refresh token for each ``client_id``, or you might use a redditor's
|
||||
``username`` if you're managing multiple users' authentications.
|
||||
|
||||
"""
|
||||
super().__init__()
|
||||
import sqlite3
|
||||
|
||||
self._connection = sqlite3.connect(database)
|
||||
self._connection.execute(
|
||||
"CREATE TABLE IF NOT EXISTS tokens (id, refresh_token, updated_at)"
|
||||
)
|
||||
self._connection.execute(
|
||||
"CREATE UNIQUE INDEX IF NOT EXISTS ux_tokens_id on tokens(id)"
|
||||
)
|
||||
self._connection.commit()
|
||||
self.key = key
|
||||
|
||||
def _get(self):
|
||||
cursor = self._connection.execute(
|
||||
"SELECT refresh_token FROM tokens WHERE id=?", (self.key,)
|
||||
)
|
||||
result = cursor.fetchone()
|
||||
if result is None:
|
||||
raise KeyError
|
||||
return result[0]
|
||||
|
||||
def _set(self, refresh_token: str):
|
||||
"""Set the refresh token in the database.
|
||||
|
||||
This function will overwrite an existing value if the corresponding ``key``
|
||||
already exists.
|
||||
|
||||
"""
|
||||
self._connection.execute(
|
||||
"REPLACE INTO tokens VALUES (?, ?, datetime('now'))",
|
||||
(self.key, refresh_token),
|
||||
)
|
||||
self._connection.commit()
|
||||
|
||||
def is_registered(self) -> bool:
|
||||
"""Return whether ``key`` already has a ``refresh_token``."""
|
||||
cursor = self._connection.execute(
|
||||
"SELECT refresh_token FROM tokens WHERE id=?", (self.key,)
|
||||
)
|
||||
return cursor.fetchone() is not None
|
||||
|
||||
def post_refresh_callback(self, authorizer: prawcore.auth.BaseAuthorizer):
|
||||
"""Update the refresh token in the database."""
|
||||
self._set(authorizer.refresh_token)
|
||||
|
||||
# While the following line is not strictly necessary, it ensures that the
|
||||
# refresh token is not used elsewhere. And also forces the pre_refresh_callback
|
||||
# to always load the latest refresh_token from the database.
|
||||
authorizer.refresh_token = None
|
||||
|
||||
def pre_refresh_callback(self, authorizer: prawcore.auth.BaseAuthorizer):
|
||||
"""Load the refresh token from the database."""
|
||||
assert authorizer.refresh_token is None
|
||||
authorizer.refresh_token = self._get()
|
||||
|
||||
def register(self, refresh_token: str) -> bool:
|
||||
"""Register the initial refresh token in the database.
|
||||
|
||||
:returns: ``True`` if ``refresh_token`` is saved to the database, otherwise,
|
||||
``False`` if there is already a ``refresh_token`` for the associated
|
||||
``key``.
|
||||
|
||||
"""
|
||||
cursor = self._connection.execute(
|
||||
"INSERT OR IGNORE INTO tokens VALUES (?, ?, datetime('now'))",
|
||||
(self.key, refresh_token),
|
||||
)
|
||||
self._connection.commit()
|
||||
return cursor.rowcount == 1
|
||||
Reference in New Issue
Block a user