Skip to content

Commit

Permalink
Merge pull request #199 from praw-dev/deprecate_args
Browse files Browse the repository at this point in the history
Deprecate positional arguments
  • Loading branch information
LilSpazJoekp committed Oct 17, 2022
2 parents 5fefb7c + bef087a commit 8ffcb7a
Show file tree
Hide file tree
Showing 80 changed files with 1,864 additions and 951 deletions.
5 changes: 5 additions & 0 deletions CHANGES.rst
Expand Up @@ -25,6 +25,11 @@ Unreleased

- The ``after`` argument for :meth:`.conversations` will now have to be included in
``params`` keyword argument.
- Positional keyword arguments for applicable functions and methods. Starting with Async
PRAW 8, most functions and methods will no longer support positional arguments. It
will encourage more explicit argument passing, enable arguments to be sorted
alphabetically, and prevent breaking changes when adding new arguments to existing
methods.

7.5.0 (2021/11/13)
------------------
Expand Down
2 changes: 1 addition & 1 deletion README.rst
Expand Up @@ -91,7 +91,7 @@ With the ``reddit`` instance you can then interact with Reddit:
# Reply to the first comment of a weekly top thread of a moderated community
subreddit = await reddit.subreddit("mod")
async for submission in subreddit.top("week"):
async for submission in subreddit.top(time_filter="week"):
comments = await submission.comments()
await comments[0].reply("An automated reply")
Expand Down
8 changes: 4 additions & 4 deletions asyncpraw/config.py
Expand Up @@ -36,7 +36,7 @@ def _config_boolean(item):
return item.lower() in {"1", "yes", "true", "on"}

@classmethod
def _load_config(cls, config_interpolation: Optional[str] = None):
def _load_config(cls, *, config_interpolation: Optional[str] = None):
"""Attempt to load settings from various praw.ini files."""
if config_interpolation is not None:
interpolator_class = cls.INTERPOLATION_LEVEL[config_interpolation]()
Expand Down Expand Up @@ -78,7 +78,7 @@ def __init__(
"""Initialize a :class:`.Config` instance."""
with Config.LOCK:
if Config.CONFIG is None:
self._load_config(config_interpolation)
self._load_config(config_interpolation=config_interpolation)

self._settings = settings
self.custom = dict(Config.CONFIG.items(site_name), **settings)
Expand All @@ -94,7 +94,7 @@ def _fetch(self, key):
del self.custom[key]
return value

def _fetch_default(self, key, default=None):
def _fetch_default(self, key, *, default=None):
if key not in self.custom:
return default
return self._fetch(key)
Expand All @@ -115,7 +115,7 @@ def _initialize_attributes(self):
self._fetch_or_not_set("check_for_updates")
)
self.warn_comment_sort = self._config_boolean(
self._fetch_default("warn_comment_sort", True)
self._fetch_default("warn_comment_sort", default=True)
)
self.kinds = {
x: self._fetch(f"{x}_kind")
Expand Down
22 changes: 14 additions & 8 deletions asyncpraw/exceptions.py
Expand Up @@ -11,6 +11,8 @@
from typing import List, Optional, Union
from warnings import warn

from .util import _deprecate_args


class AsyncPRAWException(Exception):
"""The base Async PRAW Exception that all other exception classes extend."""
Expand All @@ -32,17 +34,19 @@ def error_message(self) -> str:
error_str += f" on field {self.field!r}"
return error_str

@_deprecate_args("error_type", "message", "field")
def __init__(
self,
error_type: str,
message: Optional[str] = None,
*,
field: Optional[str] = None,
message: Optional[str] = None,
):
"""Initialize a :class:`.RedditErrorItem` instance.
:param error_type: The error type set on Reddit's end.
:param message: The associated message for the error.
:param field: The input field associated with the error, if available.
:param message: The associated message for the error.
"""
self.error_type = error_type
Expand Down Expand Up @@ -88,9 +92,9 @@ def parse_exception_list(exceptions: List[Union[RedditErrorItem, List[str]]]):
exception
if isinstance(exception, RedditErrorItem)
else RedditErrorItem(
exception[0],
exception[1] if bool(exception[1]) else "",
exception[2] if bool(exception[2]) else "",
error_type=exception[0],
field=exception[2] if bool(exception[2]) else "",
message=exception[1] if bool(exception[1]) else "",
)
for exception in exceptions
]
Expand Down Expand Up @@ -210,7 +214,8 @@ def __init__(self):
class InvalidURL(ClientException):
"""Indicate exceptions where an invalid URL is entered."""

def __init__(self, url: str, message: str = "Invalid URL: {}"):
@_deprecate_args("url", "message")
def __init__(self, url: str, *, message: str = "Invalid URL: {}"):
"""Initialize an :class:`.InvalidURL` instance.
:param url: The invalid URL.
Expand All @@ -232,11 +237,12 @@ class ReadOnlyException(ClientException):
class TooLargeMediaException(ClientException):
"""Indicate exceptions from uploading media that's too large."""

def __init__(self, maximum_size: int, actual: int):
@_deprecate_args("maximum_size", "actual")
def __init__(self, *, actual: int, maximum_size: int):
"""Initialize a :class:`.TooLargeMediaException` instance.
:param maximum_size: The maximum_size size of the uploaded media.
:param actual: The actual size of the uploaded media.
:param maximum_size: The maximum size of the uploaded media.
"""
self.maximum_size = maximum_size
Expand Down
18 changes: 11 additions & 7 deletions asyncpraw/models/auth.py
Expand Up @@ -9,6 +9,7 @@
)

from ..exceptions import InvalidImplicitAuth, MissingRequiredAttributeException
from ..util import _deprecate_args
from .base import AsyncPRAWBase


Expand Down Expand Up @@ -60,7 +61,8 @@ async def authorize(self, code: str) -> Optional[str]:
self._reddit._core = self._reddit._authorized_core = authorized_session
return authorizer.refresh_token

def implicit(self, access_token: str, expires_in: int, scope: str) -> None:
@_deprecate_args("access_token", "expires_in", "scope")
def implicit(self, *, access_token: str, expires_in: int, scope: str) -> None:
"""Set the active authorization to be an implicit authorization.
:param access_token: The access_token obtained from Reddit's callback.
Expand Down Expand Up @@ -95,19 +97,17 @@ async def scopes(self) -> Set[str]:
await authorizer.refresh()
return authorizer.scopes

@_deprecate_args("scopes", "state", "duration", "implicit")
def url(
self,
scopes: List[str],
state: str,
*,
duration: str = "permanent",
implicit: bool = False,
scopes: List[str],
state: str,
) -> str:
"""Return the URL used out-of-band to grant access to your application.
:param scopes: A list of OAuth scopes to request authorization for.
:param state: A string that will be reflected in the callback to
``redirect_uri``. This value should be temporarily unique to the client for
whom the URL was generated for.
:param duration: Either ``"permanent"`` or ``"temporary"`` (default:
``"permanent"``). ``"temporary"`` authorizations generate access tokens that
last only 1 hour. ``"permanent"`` authorizations additionally ``permanent``
Expand All @@ -117,6 +117,10 @@ def url(
:param implicit: For **installed** applications, this value can be set to use
the implicit, rather than the code flow. When ``True``, the ``duration``
argument has no effect as only temporary tokens can be retrieved.
:param scopes: A list of OAuth scopes to request authorization for.
:param state: A string that will be reflected in the callback to
``redirect_uri``. This value should be temporarily unique to the client for
whom the URL was generated for.
"""
authenticator = self._reddit._read_only_core._authorizer._authenticator
Expand Down
8 changes: 4 additions & 4 deletions asyncpraw/models/base.py
Expand Up @@ -10,17 +10,17 @@ class AsyncPRAWBase:
"""Superclass for all models in Async PRAW."""

@staticmethod
def _safely_add_arguments(argument_dict, key, **new_arguments):
"""Replace argument_dict[key] with a deepcopy and update.
def _safely_add_arguments(*, arguments, key, **new_arguments):
"""Replace arguments[key] with a deepcopy and update.
This method is often called when new parameters need to be added to a request.
By calling this method and adding the new or updated parameters we can insure we
don't modify the dictionary passed in by the caller.
"""
value = deepcopy(argument_dict[key]) if key in argument_dict else {}
value = deepcopy(arguments[key]) if key in arguments else {}
value.update(new_arguments)
argument_dict[key] = value
arguments[key] = value

@classmethod
def parse(cls, data: Dict[str, Any], reddit: "asyncpraw.Reddit") -> Any:
Expand Down
12 changes: 8 additions & 4 deletions asyncpraw/models/comment_forest.py
Expand Up @@ -5,6 +5,7 @@
from warnings import warn

from ..exceptions import DuplicateReplaceException
from ..util import _deprecate_args
from .reddit.more import MoreComments

if TYPE_CHECKING: # pragma: no cover
Expand All @@ -19,7 +20,7 @@ class CommentForest:
"""

@staticmethod
def _gather_more_comments(tree, parent_tree=None):
def _gather_more_comments(tree, *, parent_tree=None):
"""Return a list of :class:`.MoreComments` objects obtained from tree."""
more_comments = []
queue = [(None, x) for x in tree]
Expand Down Expand Up @@ -178,10 +179,11 @@ async def async_func():
return async_func()
return comments

@_deprecate_args("limit", "threshold")
async def replace_more(
self, limit: Optional[int] = 32, threshold: int = 0
self, *, limit: int = 32, threshold: int = 0
) -> List["asyncpraw.models.MoreComments"]:
"""Update the comment forest by resolving instances of MoreComments.
"""Update the comment forest by resolving instances of :class:`.MoreComments`.
:param limit: The maximum number of :class:`.MoreComments` instances to replace.
Each replacement requires 1 API request. Set to ``None`` to have no limit,
Expand Down Expand Up @@ -252,7 +254,9 @@ async def replace_more(
remaining -= 1

# Add new MoreComment objects to the heap of more_comments
for more in self._gather_more_comments(new_comments, self._comments):
for more in self._gather_more_comments(
new_comments, parent_tree=self._comments
):
more.submission = self._submission
heappush(more_comments, more)
# Insert all items into the tree
Expand Down
42 changes: 30 additions & 12 deletions asyncpraw/models/helpers.py
Expand Up @@ -3,6 +3,7 @@
from typing import TYPE_CHECKING, AsyncGenerator, List, Optional, Union

from ..const import API_PATH
from ..util import _deprecate_args
from .base import AsyncPRAWBase
from .reddit.draft import Draft
from .reddit.live import LiveThread
Expand All @@ -23,7 +24,7 @@ class DraftHelper(AsyncPRAWBase):
"""

async def __call__(
self, *, draft_id: Optional[str] = None, fetch: bool = True
self, draft_id: Optional[str] = None, fetch: bool = True
) -> Union[List["asyncpraw.models.Draft"], "asyncpraw.models.Draft"]:
"""Return a list of :class:`.Draft` instances.
Expand All @@ -40,7 +41,7 @@ async def __call__(
.. code-block:: python
draft_id = "124862bc-e1e9-11eb-aa4f-e68667a77cbb"
draft = await reddit.drafts(draft_id=draft_id)
draft = await reddit.drafts(draft_id)
print(draft)
"""
Expand Down Expand Up @@ -223,14 +224,16 @@ async def generator():

return generator()

@_deprecate_args("title", "description", "nsfw", "resources")
async def create(
self,
title: str,
*,
description: Optional[str] = None,
nsfw: bool = False,
resources: str = None,
) -> "asyncpraw.models.LiveThread":
r"""Create a new :class:`.LiveThread`.
"""Create a new :class:`.LiveThread`.
:param title: The title of the new :class:`.LiveThread`.
:param description: The new :class:`.LiveThread`'s description.
Expand Down Expand Up @@ -271,23 +274,26 @@ async def now(self) -> Optional["asyncpraw.models.LiveThread"]:
class MultiredditHelper(AsyncPRAWBase):
"""Provide a set of functions to interact with multireddits."""

@_deprecate_args("redditor", "name")
async def __call__(
self,
redditor: Union[str, "asyncpraw.models.Redditor"],
*,
name: str,
redditor: Union[str, "asyncpraw.models.Redditor"],
fetch: bool = False,
) -> "asyncpraw.models.Multireddit":
"""Return an instance of :class:`.Multireddit`.
"""Return a lazy instance of :class:`.Multireddit`.
If you need the object fetched right away (e.g., to access an attribute) you can
do:
.. code-block:: python
multireddit = await reddit.multireddit("redditor", "multi", fetch=True)
multireddit = await reddit.multireddit(redditor="redditor", name="multi", fetch=True)
async for comment in multireddit.comments(limit=25):
print(comment.author)
:param name: The name of the multireddit.
:param redditor: A redditor name or :class:`.Redditor` instance who owns the
multireddit.
:param name: The name of the multireddit.
Expand All @@ -301,13 +307,23 @@ async def __call__(
await multireddit._fetch()
return multireddit

@_deprecate_args(
"display_name",
"subreddits",
"description_md",
"icon_name",
"key_color",
"visibility",
"weighting_scheme",
)
async def create(
self,
display_name: str,
subreddits: Union[str, "asyncpraw.models.Subreddit"],
*,
description_md: Optional[str] = None,
display_name: str,
icon_name: Optional[str] = None,
key_color: Optional[str] = None,
subreddits: Union[str, "asyncpraw.models.Subreddit"],
visibility: str = "private",
weighting_scheme: str = "classic",
) -> "asyncpraw.models.Multireddit":
Expand Down Expand Up @@ -380,25 +396,27 @@ async def __call__(
await subreddit._fetch()
return subreddit

@_deprecate_args("name", "title", "link_type", "subreddit_type", "wikimode")
async def create(
self,
name: str,
title: Optional[str] = None,
*,
link_type: str = "any",
subreddit_type: str = "public",
title: Optional[str] = None,
wikimode: str = "disabled",
**other_settings: Optional[str],
) -> "asyncpraw.models.Subreddit":
"""Create a new :class:`.Subreddit`.
:param name: The name for the new subreddit.
:param title: The title of the subreddit. When ``None`` or ``""`` use the value
of ``"name"``.
:param link_type: The types of submissions users can make. One of ``"any"``,
``"link"``, or ``"self"`` (default: ``"any"``).
:param subreddit_type: One of ``"archived"``, ``"employees_only"``,
``"gold_only"``, ``"gold_restricted"``, ``"private"``, ``"public"``, or
``"restricted"`` (default: ``"public"``).
:param title: The title of the subreddit. When ``None`` or ``""`` use the value
of ``name``.
:param wikimode: One of ``"anyone"``, ``"disabled"``, or ``"modonly"`` (default:
``"disabled"``).
Expand All @@ -413,8 +431,8 @@ async def create(
"""
await Subreddit._create_or_update(
_reddit=self._reddit,
name=name,
link_type=link_type,
name=name,
subreddit_type=subreddit_type,
title=title or name,
wikimode=wikimode,
Expand Down

0 comments on commit 8ffcb7a

Please sign in to comment.