Migrating to Async PRAW 8
Async PRAW 8 completes the deprecation cycle started during the 7.x series. This guide
covers every removed or changed item, with examples showing how to update your code.
Python Version Support
Async PRAW 8 requires Python 3.10 or newer. Support for Python 3.8 and 3.9, both of
which are end-of-life, has been dropped, and support for Python 3.13 and 3.14 has been
added.
Dependency and Behavior Changes
Async PRAW 8 raises several dependency floors: it now requires asyncprawcore >=4, <5
(for its public Session and authorizer accessors and the widened
Session.request() annotations) and update_checker[async] >=1.0, <2. The 1.0
release of update_checker is dependency-free, dropping requests from Async
PRAW’s transitive dependencies; the update check now uses the native async API on the
first request, instead of a blocking call during Reddit initialization. These
requirements are resolved automatically when you install or upgrade Async PRAW; no code
changes are required.
Async PRAW now ships a PEP 561 py.typed marker, so downstream projects can type
check against Async PRAW’s inline annotations. This is additive and does not change
runtime behavior.
Async PRAW now warns at initialization if a praw.ini file in the current working
directory sets the oauth_url or reddit_url endpoint, since such a file can
redirect credentials to an untrusted host. If you intentionally override these endpoints
(for example, to test against a mock server), silence the warning by setting the
PRAW_ALLOW_ENDPOINT_OVERRIDE environment variable.
Because a Submission is fetched on initialization, its comment_sort and
comment_limit attributes – and Submission.add_fetch_param() – must be set
before it is fetched. Initialize the submission with fetch=False, set them, then
call load(). Setting any of them after the submission has been
fetched now raises ClientException instead of logging a warning. The
warn_comment_sort and warn_additional_fetch_params configuration options, which
previously toggled these warnings, have been removed.
Old:
submission = await reddit.submission("id")
submission.comment_sort = "new"
await submission.load()
New:
submission = await reddit.submission("id", fetch=False)
submission.comment_sort = "new"
await submission.load()
Unified submit method
Subreddit.submit_gallery, Subreddit.submit_image, Subreddit.submit_poll, and
Subreddit.submit_video have been merged into Subreddit.submit(). The kind of
submission is selected with the gallery, image, poll, url, or video
keyword argument. At least one of those, or selftext, must be provided, and they are
mutually exclusive, while selftext may accompany any of them as optional
Markdown-formatted body text.
Submitting an image
Old:
subreddit = await reddit.subreddit("test")
await subreddit.submit_image("My Title", "/path/to/image.png")
New:
from asyncpraw.models import PostMedia
subreddit = await reddit.subreddit("test")
await subreddit.submit("My Title", image=PostMedia("/path/to/image.png"))
Submitting a video
Video-specific options, such as a custom thumbnail or submitting the video as a
videogif, are provided by passing a dict instead of a bare PostMedia.
Old:
subreddit = await reddit.subreddit("test")
await subreddit.submit_video(
"My Title", "/path/to/video.mp4", thumbnail_path="/path/to/thumbnail.png"
)
New:
from asyncpraw.models import PostMedia
subreddit = await reddit.subreddit("test")
await subreddit.submit(
"My Title",
video={
"media": PostMedia("/path/to/video.mp4"),
"thumbnail": PostMedia("/path/to/thumbnail.png"),
},
)
The videogif argument is now the "gif" key of the video dict:
Old:
subreddit = await reddit.subreddit("test")
await subreddit.submit_video("My Title", "/path/to/video.mp4", videogif=True)
New:
from asyncpraw.models import PostMedia
subreddit = await reddit.subreddit("test")
await subreddit.submit(
"My Title", video={"gif": True, "media": PostMedia("/path/to/video.mp4")}
)
Submitting a gallery
Gallery items are either a bare PostMedia, or a dict with a media key
(replacing image_path) when a caption or outbound_url is desired.
Old:
images = [
{"image_path": "/path/to/image.png"},
{"image_path": "/path/to/image2.png", "caption": "a caption"},
]
subreddit = await reddit.subreddit("test")
await subreddit.submit_gallery("My Title", images)
New:
from asyncpraw.models import PostMedia
gallery = [
PostMedia("/path/to/image.png"),
{"caption": "a caption", "media": PostMedia("/path/to/image2.png")},
]
subreddit = await reddit.subreddit("test")
await subreddit.submit("My Title", gallery=gallery)
Submitting a poll
The options and duration arguments are now keys of the poll dict.
selftext is no longer required for polls.
Old:
subreddit = await reddit.subreddit("test")
await subreddit.submit_poll(
"Do you like Async PRAW?", duration=3, options=["Yes", "No"], selftext=""
)
New:
subreddit = await reddit.subreddit("test")
await subreddit.submit(
"Do you like Async PRAW?", poll={"duration": 3, "options": ["Yes", "No"]}
)
user.me() in read-only mode
Calling await reddit.user.me() in read_only mode previously returned
None with a deprecation warning. It now raises ReadOnlyException.
Old:
if await reddit.user.me() is None:
print("Not authenticated")
New:
from asyncpraw.exceptions import ReadOnlyException
try:
await reddit.user.me()
except ReadOnlyException:
print("Not authenticated")
Redditor.subreddit is a UserSubreddit
The subreddit attribute of Redditor is a UserSubreddit instance.
Its values are accessed as attributes; the dict interface has been removed.
Old:
title = redditor.subreddit["title"]
New:
title = redditor.subreddit.title
Link submissions with body text
The selftext and url arguments to Subreddit.submit() are no longer
mutually exclusive. When url is provided, selftext is used as optional
Markdown-formatted body text to accompany the link submission. The same applies to
gallery, image, poll, and video submissions.
One exception: combining inline_media with selftext for a url submission
raises an exception, because Reddit does not support inline media in body text for link
submissions.
Arguments that must be passed by keyword
The following arguments must now be passed by keyword:
The mark_read argument when calling subreddit.modmail to fetch a
ModmailConversation, e.g., await subreddit.modmail("2gmz",
mark_read=True).
The is_link argument to SubredditFlairTemplates.flair_type().
The data argument to Objector.objectify.
The fetch argument to get_emoji(), get_reason(),
get_update(), DraftHelper.__call__(), LiveHelper.__call__(),
Modmail.__call__(), SubredditCollections.__call__(), and
SubredditHelper.__call__().
Arguments that must be passed positionally
The following arguments are now positional-only:
lazy keyword argument replaced by fetch
The lazy keyword argument has been replaced by fetch across the codebase to
consolidate the keyword used to explicitly control fetching when initializing an object.
fetch=True performs the fetch immediately, while fetch=False leaves the object
lazy. See Lazy Loading for the per-class defaults.
Old:
submission = await reddit.submission("id", lazy=True)
New:
submission = await reddit.submission("id", fetch=False)
Reddit synchronous context manager removed
Reddit can no longer be used as a synchronous context manager. Use the
asynchronous context manager (async with) instead, which closes the underlying
aiohttp session on exit.
Old:
with asyncpraw.Reddit(...) as reddit:
...
New:
async with asyncpraw.Reddit(...) as reddit:
...
Old modmail
Reddit retired the old modmail system, so Subreddit.mod.inbox,
Subreddit.mod.unread, Subreddit.mod.stream.unread, SubredditMessage.mute,
and SubredditMessage.unmute have been removed. Use the new modmail interface
instead.
Old:
subreddit = await reddit.subreddit("test")
async for message in subreddit.mod.unread():
print(message.subject)
New:
subreddit = await reddit.subreddit("test")
async for conversation in subreddit.modmail.conversations(state="new"):
print(conversation.subject)
To stream conversations, use SubredditModerationStream.modmail_conversations()
(subreddit.mod.stream.modmail_conversations()). Muting and unmuting users is
available on ModmailConversation via ModmailConversation.mute() and
ModmailConversation.unmute().
The after argument for conversations() has also been removed. To resume a
listing from a known conversation, pass after through params:
subreddit = await reddit.subreddit("test")
conversations = subreddit.modmail.conversations(params={"after": "2gmz"})
Token managers
The token_manager keyword argument to Reddit, along with
BaseTokenManager, FileTokenManager, and SQLiteTokenManager, has been
removed. Refresh tokens no longer rotate, so a static refresh_token can be provided
directly.
Old:
from asyncpraw.util.token_manager import FileTokenManager
reddit = asyncpraw.Reddit(..., token_manager=FileTokenManager("token.txt"))
New:
reddit = asyncpraw.Reddit(..., refresh_token="...")
See Working with Refresh Tokens for the complete guide to obtaining and using refresh tokens.
Subreddit Module Reorganization
The single asyncpraw/models/reddit/subreddit.py file has been split into an
asyncpraw.models.reddit.subreddit package. The Subreddit class and each of
its helper classes now live in their own module:
All of these classes remain importable from asyncpraw.models.reddit.subreddit and
asyncpraw.models for backwards compatibility, so existing imports of the form from
asyncpraw.models.reddit.subreddit import Subreddit continue to work unchanged. Update
direct imports only if you were depending on the module file’s location on disk (for
example, in tooling or monkeypatches).
Removed without replacement
The following were removed because Reddit no longer supports the underlying endpoints or
features:
Reddit.random_subreddit, Subreddit.random, and Subreddit.random_rising –
Reddit removed the random subreddit and random submission endpoints.
Comment.award, Submission.award, and the gild aliases on
Comment, Redditor, and Submission – Reddit removed the
awards API.
Redditor.gilded, Subreddit.gilded, and Redditor.gildings – gilded
listings were removed along with the awards API.
Subreddits.search_by_topic – the endpoint has returned 404 since 2020.
Reddit.validate_on_submit – Reddit now always validates posts on submission, so
the setting no longer has any effect. Remove any assignments to it.
WebSocketException.original_exception.
InboxableMixin.unblock_subreddit.
The reset_timestamp key in the dictionary returned by limits() – the
remaining keys are remaining and used.