Python Reddit API Wrapper - simple access to Reddit's API
—
PRAW provides comprehensive OAuth 2.0 authentication support for both script applications and web applications. It handles token management, refresh tokens, and supports multiple authentication flows while maintaining security and following Reddit's API guidelines.
Handle OAuth 2.0 authentication flows and token management.
class Auth:
def __init__(self, reddit): ...
def authorize(self, code: str):
"""
Complete OAuth 2.0 authorization with authorization code.
Parameters:
- code: Authorization code from Reddit OAuth callback
This method exchanges the authorization code for access and refresh tokens.
Used in web application authentication flow.
"""
def implicit(
self,
access_token: str,
expires_in: int,
scope: str
):
"""
Set implicit OAuth 2.0 authorization.
Parameters:
- access_token: Access token from implicit flow
- expires_in: Token expiration time in seconds
- scope: Space-separated list of granted scopes
Used for implicit grant flow (less secure, not recommended).
"""
def url(
self,
scopes: list,
state: str,
*,
duration: str = "temporary",
implicit: bool = False,
**kwargs
) -> str:
"""
Generate OAuth 2.0 authorization URL.
Parameters:
- scopes: List of requested scopes
- state: Random string to prevent CSRF attacks
- duration: "temporary" or "permanent" token duration
- implicit: Use implicit grant flow
Returns:
Authorization URL for user to visit
"""
def revoke_token(
self,
token: str = None,
*,
revoke_refresh: bool = True,
**kwargs
):
"""
Revoke access or refresh token.
Parameters:
- token: Token to revoke (defaults to current access token)
- revoke_refresh: Also revoke refresh token
Invalidates the specified token on Reddit's servers.
"""
def scopes(self) -> set:
"""
Get currently authorized scopes.
Returns:
Set of scope strings currently granted
"""For personal scripts and automation tools running on your own machine.
# Script authentication configuration
reddit = praw.Reddit(
client_id="your_client_id",
client_secret="your_client_secret",
username="your_username",
password="your_password",
user_agent="Script Name v1.0 by /u/yourusername"
)
# Authentication is automatic - no additional steps needed
# All scopes are automatically granted for script appsFor web applications that need user authorization with specific scopes.
# Step 1: Initialize Reddit instance for web app
reddit = praw.Reddit(
client_id="your_client_id",
client_secret="your_client_secret",
redirect_uri="http://localhost:8080/callback",
user_agent="Web App v1.0 by /u/yourusername"
)
# Step 2: Generate authorization URL
scopes = ["identity", "read", "submit", "edit"]
state = "random_string_for_csrf_protection"
auth_url = reddit.auth.url(scopes, state, duration="permanent")
# Step 3: Redirect user to auth_url, they authorize and return with code
# Step 4: Exchange code for tokens
reddit.auth.authorize(code_from_callback)
# Step 5: Reddit instance is now authenticated
authenticated_user = reddit.user.me()For applications that only need to read public data.
# Read-only mode (no user authentication required)
reddit = praw.Reddit(
client_id="your_client_id",
client_secret="your_client_secret",
user_agent="Read-Only App v1.0 by /u/yourusername"
)
# Enable read-only mode
reddit.read_only = True
# Can now access public content without user authentication
subreddit = reddit.subreddit("python")
for post in subreddit.hot(limit=10):
print(post.title)Reddit OAuth scopes define what actions your application can perform.
# Available OAuth scopes
SCOPES = {
"identity": "Access user identity (username, karma, creation date)",
"edit": "Edit and delete user's comments and submissions",
"flair": "Manage user and link flair",
"history": "Access user's post and comment history",
"modconfig": "Manage configuration and sidebar of subreddits",
"modflair": "Manage flair templates and flair of other users",
"modlog": "Access moderation log",
"modposts": "Approve, remove, mark nsfw, and distinguish content",
"modwiki": "Change wiki settings and edit wiki pages",
"mysubreddits": "Access user's subscribed subreddits",
"privatemessages": "Access and send private messages",
"read": "Read posts and comments",
"report": "Report content for rule violations",
"save": "Save and unsave posts and comments",
"submit": "Submit posts and comments",
"subscribe": "Subscribe and unsubscribe from subreddits",
"vote": "Vote on posts and comments",
"wikiedit": "Edit wiki pages",
"wikiread": "Read wiki pages"
}
# Example: Request multiple scopes
scopes = ["identity", "read", "submit", "edit", "vote"]
auth_url = reddit.auth.url(scopes, state)PRAW automatically handles token refresh when using permanent tokens.
# Automatic token refresh (handled internally by PRAW)
# When access token expires, PRAW uses refresh token automatically
# Check current scopes
current_scopes = reddit.auth.scopes()
print(f"Authorized scopes: {current_scopes}")
# Manually revoke tokens if needed
reddit.auth.revoke_token() # Revoke current access token
reddit.auth.revoke_token(revoke_refresh=True) # Also revoke refresh tokenImplement custom token storage and retrieval mechanisms.
class BaseTokenManager:
"""Abstract base class for token managers."""
def post_refresh_callback(self, authorizer):
"""Called after token refresh."""
pass
def pre_refresh_callback(self, authorizer):
"""Called before token refresh."""
pass
class FileTokenManager(BaseTokenManager):
"""File-based token storage."""
def __init__(self, filename: str): ...
class SQLiteTokenManager(BaseTokenManager):
"""SQLite-based token storage."""
def __init__(self, database: str, key: str): ...
# Use custom token manager
token_manager = FileTokenManager("tokens.json")
reddit = praw.Reddit(
client_id="your_client_id",
client_secret="your_client_secret",
redirect_uri="your_redirect_uri",
user_agent="your_user_agent",
token_manager=token_manager
)Configure authentication using environment variables for security.
# Set environment variables
# praw_client_id=your_client_id
# praw_client_secret=your_client_secret
# praw_username=your_username
# praw_password=your_password
# praw_user_agent=your_user_agent
# PRAW automatically uses environment variables
reddit = praw.Reddit()
# Or specify which environment variables to use
reddit = praw.Reddit(
client_id=os.environ["MY_CLIENT_ID"],
client_secret=os.environ["MY_CLIENT_SECRET"],
username=os.environ["MY_USERNAME"],
password=os.environ["MY_PASSWORD"],
user_agent=os.environ["MY_USER_AGENT"]
)Use praw.ini configuration files for different environments.
# praw.ini file format
"""
[DEFAULT]
user_agent=MyBot v1.0 by /u/yourusername
[development]
client_id=dev_client_id
client_secret=dev_client_secret
username=dev_username
password=dev_password
[production]
client_id=prod_client_id
client_secret=prod_client_secret
username=prod_username
password=prod_password
"""
# Load specific configuration
reddit = praw.Reddit("development") # Uses [development] section
reddit = praw.Reddit("production") # Uses [production] section
# Override specific settings
reddit = praw.Reddit(
"development",
username="override_username" # Override just username
)Check authentication status and handle authentication errors.
# Check if authenticated
try:
user = reddit.user.me()
print(f"Authenticated as: {user.name}")
is_authenticated = True
except AttributeError:
print("Not authenticated")
is_authenticated = False
# Check read-only mode
if reddit.read_only:
print("In read-only mode")
else:
print("In authenticated mode")
# Get current scopes
if not reddit.read_only:
scopes = reddit.auth.scopes()
print(f"Available scopes: {scopes}")
# Check for specific scope
if "submit" in scopes:
print("Can submit posts")
if "modposts" in scopes:
print("Can moderate posts")Handle authentication-related errors appropriately.
from praw.exceptions import (
InvalidImplicitAuth,
ReadOnlyException,
RedditAPIException
)
try:
# Attempt authenticated operation
reddit.subreddit("test").submit("title", selftext="content")
except ReadOnlyException:
print("Cannot submit in read-only mode")
except InvalidImplicitAuth:
print("Invalid implicit authentication")
except RedditAPIException as e:
# Handle specific Reddit API errors
for error in e.items:
if error.error_type == "NO_PERMISSION":
print("Insufficient permissions")
elif error.error_type == "INVALID_CREDENTIALS":
print("Invalid authentication credentials")import praw
import os
# Method 1: Direct configuration
reddit = praw.Reddit(
client_id="your_client_id",
client_secret="your_client_secret",
username="your_username",
password="your_password",
user_agent="MyScript v1.0 by /u/yourusername"
)
# Method 2: Environment variables
reddit = praw.Reddit(
client_id=os.environ["PRAW_CLIENT_ID"],
client_secret=os.environ["PRAW_CLIENT_SECRET"],
username=os.environ["PRAW_USERNAME"],
password=os.environ["PRAW_PASSWORD"],
user_agent=os.environ["PRAW_USER_AGENT"]
)
# Verify authentication
me = reddit.user.me()
print(f"Authenticated as: {me.name}")import praw
from flask import Flask, request, redirect, session
import secrets
app = Flask(__name__)
app.secret_key = "your-secret-key"
reddit = praw.Reddit(
client_id="your_client_id",
client_secret="your_client_secret",
redirect_uri="http://localhost:5000/callback",
user_agent="WebApp v1.0 by /u/yourusername"
)
@app.route("/login")
def login():
# Generate state for CSRF protection
state = secrets.token_urlsafe(32)
session["oauth_state"] = state
# Request scopes
scopes = ["identity", "read", "submit"]
# Generate authorization URL
auth_url = reddit.auth.url(scopes, state, duration="permanent")
return redirect(auth_url)
@app.route("/callback")
def callback():
# Verify state parameter
if request.args.get("state") != session.get("oauth_state"):
return "Invalid state parameter", 400
# Get authorization code
code = request.args.get("code")
if not code:
return "No authorization code received", 400
# Exchange code for tokens
reddit.auth.authorize(code)
# Get authenticated user
user = reddit.user.me()
session["reddit_username"] = user.name
return f"Successfully authenticated as {user.name}"
@app.route("/protected")
def protected():
if "reddit_username" not in session:
return redirect("/login")
# Use authenticated Reddit instance
username = session["reddit_username"]
user = reddit.redditor(username)
return f"Hello {username}! Your karma: {user.comment_karma}"import praw
from praw.util.token_manager import FileTokenManager
# Use file-based token storage
token_manager = FileTokenManager("reddit_tokens.json")
reddit = praw.Reddit(
client_id="your_client_id",
client_secret="your_client_secret",
redirect_uri="your_redirect_uri",
user_agent="your_user_agent",
token_manager=token_manager
)
# First time: perform OAuth flow
if not hasattr(reddit.auth, 'refresh_token'):
scopes = ["identity", "read", "submit"]
state = "random_state"
auth_url = reddit.auth.url(scopes, state)
print(f"Visit: {auth_url}")
code = input("Enter authorization code: ")
reddit.auth.authorize(code)
# Subsequent runs: tokens are loaded automatically
user = reddit.user.me()
print(f"Authenticated as: {user.name}")
# Check authorized scopes
scopes = reddit.auth.scopes()
print(f"Authorized scopes: {scopes}")
# Revoke tokens when done
reddit.auth.revoke_token(revoke_refresh=True)class Config:
"""PRAW configuration management."""
CONFIG_NOT_SET: str # Sentinel value for unset configuration
def __init__(
self,
site_name: str = None,
interpolation: str = None,
**settings
): ...
class InvalidImplicitAuth(Exception):
"""Raised when implicit authentication is used incorrectly."""
class ReadOnlyException(Exception):
"""Raised when attempting write operations in read-only mode."""
class InvalidURL(Exception):
"""Raised when an invalid URL is encountered."""
class MissingRequiredAttributeException(Exception):
"""Raised when required configuration is missing."""Install with Tessl CLI
npx tessl i tessl/pypi-praw