Migrating to PRAW 8

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

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.

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 praw.models import PostMedia

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

Submitting an image

All arguments to Subreddit.submit_image(), including title, must now be passed by keyword.

Old:

reddit.subreddit("test").submit_image("My Title", "/path/to/image.png")

New:

from praw.models import PostMedia

reddit.subreddit("test").submit_image(
    image_media=PostMedia("/path/to/image.png"), title="My Title"
)

Submitting a video

All arguments to Subreddit.submit_video(), including title, must now be passed by keyword.

Old:

reddit.subreddit("test").submit_video(
    "My Title", "/path/to/video.mp4", thumbnail_path="/path/to/thumbnail.png"
)

New:

from praw.models import PostMedia

reddit.subreddit("test").submit_video(
    thumbnail_media=PostMedia("/path/to/thumbnail.png"),
    title="My Title",
    video_media=PostMedia("/path/to/video.mp4"),
)

Inline media

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

Old:

from praw.models import InlineImage

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

New:

from praw.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 emoji_media, which takes an EmojiMedia instance.

Old:

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

New:

from praw.models import EmojiMedia

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

Stylesheet images

The image_path arguments to the SubredditStylesheet upload_* methods have been replaced by image_media. 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. Other than SubredditStylesheet.upload(), the image_media argument must be passed positionally.

Old:

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

New:

from praw.models import StylesheetAsset, StylesheetImage

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

Widget images

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

Old:

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

New:

from praw.models import WidgetMedia

image_url = reddit.subreddit("test").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 prawcore.RequestException on transport errors, consistent with all other requests. Code that caught raw requests exceptions around media uploads should catch prawcore.RequestException instead.

user.me() in read-only mode

Calling reddit.user.me() in read_only mode previously returned None with a deprecation warning. It now raises ReadOnlyException.

Old:

if reddit.user.me() is None:
    print("Not authenticated")

New:

from praw.exceptions import ReadOnlyException

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

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:

for message in reddit.subreddit("test").mod.unread():
    print(message.subject)

New:

for conversation in reddit.subreddit("test").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:

conversations = reddit.subreddit("test").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 praw.util.token_manager import FileTokenManager

reddit = praw.Reddit(..., token_manager=FileTokenManager("token.txt"))

New:

reddit = praw.Reddit(..., refresh_token="...")

See Working with Refresh Tokens for the complete guide to obtaining and using refresh tokens.

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.