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()

Media Uploads

All methods that upload media now accept Media instances instead of file paths. The relevant subclass depends on what is being uploaded: PostMedia for submissions and inline media, EmojiMedia for emoji, StylesheetImage and StylesheetAsset for stylesheet images, and WidgetMedia for widget images.

A Media instance can be constructed from a file path, or from bytes content along with a name, so media no longer has to be written to disk before uploading:

from asyncpraw.models import PostMedia

media_from_path = PostMedia("/path/to/image.png")
media_from_bytes = PostMedia(image_bytes, "image.png")

Inline media

The path argument to InlineMedia (InlineGif, InlineImage, and InlineVideo) has been replaced by media, which takes a PostMedia instance.

Old:

from asyncpraw.models import InlineImage

image = InlineImage(caption="optional caption", path="/path/to/image.jpg")

New:

from asyncpraw.models import InlineImage, PostMedia

image = InlineImage(caption="optional caption", media=PostMedia("/path/to/image.jpg"))

Emoji

The image_path argument to SubredditEmoji.add() has been replaced by media, which takes an EmojiMedia instance.

Old:

subreddit = await reddit.subreddit("test")
await subreddit.emoji.add(image_path="emoji.png", name="emoji")

New:

from asyncpraw.models import EmojiMedia

subreddit = await reddit.subreddit("test")
await subreddit.emoji.add(media=EmojiMedia("emoji.png"), name="emoji")

Stylesheet images

The image_path arguments to the SubredditStylesheet upload_* methods have been replaced by media, which must be passed positionally. SubredditStylesheet.upload(), upload_header(), upload_mobile_header(), and upload_mobile_icon() take a StylesheetImage instance, while upload_banner(), upload_banner_additional_image(), upload_banner_hover_image(), and upload_mobile_banner() take a StylesheetAsset instance.

Old:

subreddit = await reddit.subreddit("test")
await subreddit.stylesheet.upload(image_path="img.png", name="smile")
await subreddit.stylesheet.upload_banner("banner.png")

New:

from asyncpraw.models import StylesheetAsset, StylesheetImage

subreddit = await reddit.subreddit("test")
await subreddit.stylesheet.upload(StylesheetImage("img.png"), name="smile")
await subreddit.stylesheet.upload_banner(StylesheetAsset("banner.png"))

Widget images

The file_path argument to SubredditWidgetsModeration.upload_image() has been replaced by media, which takes a WidgetMedia instance and must be passed positionally.

Old:

subreddit = await reddit.subreddit("test")
image_url = await subreddit.widgets.mod.upload_image("/path/to/image.jpg")

New:

from asyncpraw.models import WidgetMedia

subreddit = await reddit.subreddit("test")
image_url = await subreddit.widgets.mod.upload_image(WidgetMedia("/path/to/image.jpg"))

Other media upload changes

  • An unknown media type now raises ClientException when uploading media, instead of falling back to JPEG.

  • Media uploads to Reddit’s S3 buckets now respect the configured timeout and raise asyncprawcore.RequestException on transport errors, consistent with all other requests. Code that caught raw aiohttp exceptions around media uploads should catch asyncprawcore.RequestException instead.

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 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

Arguments that must be passed by keyword

The following arguments must now be passed by keyword:

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)

CommentForest iteration model

CommentForest.list() no longer needs to be awaited – it returns the list directly. Code that previously awaited the call should drop the await.

Old:

submission = await reddit.submission("id")
comments = await submission.comments.list()

New:

submission = await reddit.submission("id")
comments = submission.comments.list()

In addition, CommentForest can no longer be used as an asynchronous iterator. Iterate over the result of list() instead:

for comment in submission.comments.list():
    ...

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:
    ...

Renamed

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:

Class

New module

Subreddit

asyncpraw.models.reddit.subreddit.subreddit

Modmail

asyncpraw.models.reddit.subreddit.modmail

SubredditFilters

asyncpraw.models.reddit.subreddit.filters

SubredditFlair, SubredditFlairTemplates, SubredditLinkFlairTemplates, SubredditRedditorFlairTemplates

asyncpraw.models.reddit.subreddit.flair

SubredditModeration, SubredditModerationStream

asyncpraw.models.reddit.subreddit.moderation

SubredditQuarantine

asyncpraw.models.reddit.subreddit.quarantine

SubredditRelationship, ContributorRelationship, ModeratorRelationship

asyncpraw.models.reddit.subreddit.relationship

SubredditStream

asyncpraw.models.reddit.subreddit.stream

SubredditStylesheet

asyncpraw.models.reddit.subreddit.stylesheet

SubredditWiki

asyncpraw.models.reddit.subreddit.wiki

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.