Comprehensive Python SDK for the AT Protocol, providing client interfaces, authentication, and real-time streaming for decentralized social networks.
—
Quality
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
High-level client interfaces providing synchronous and asynchronous access to AT Protocol services. The client classes handle authentication, session management, automatic JWT refresh, and provide access to all AT Protocol operations through a comprehensive set of generated methods.
The main synchronous client for AT Protocol operations with automatic session management and thread-safe JWT refresh.
class Client:
"""
High-level synchronous client for XRPC and ATProto operations.
Attributes:
me (Optional[models.AppBskyActorDefs.ProfileViewDetailed]): Current user profile
"""
def __init__(self, base_url: Optional[str] = None, *args, **kwargs):
"""
Initialize the client.
Args:
base_url (str, optional): Custom base URL for AT Protocol server
"""
def login(
self,
login: Optional[str] = None,
password: Optional[str] = None,
session_string: Optional[str] = None,
auth_factor_token: Optional[str] = None
) -> models.AppBskyActorDefs.ProfileViewDetailed:
"""
Authenticate with AT Protocol server.
Args:
login (str, optional): Handle or email of the account
password (str, optional): Main or app-specific password
session_string (str, optional): Session string for re-authentication
auth_factor_token (str, optional): Auth factor token for Email 2FA
Note:
Either session_string or login and password should be provided.
Returns:
AppBskyActorDefs.ProfileViewDetailed: Profile information
"""
def send_post(
self,
text: Union[str, 'client_utils.TextBuilder'],
profile_identify: Optional[str] = None,
reply_to: Optional['models.AppBskyFeedPost.ReplyRef'] = None,
embed: Optional[Union[
'models.AppBskyEmbedImages.Main',
'models.AppBskyEmbedExternal.Main',
'models.AppBskyEmbedRecord.Main',
'models.AppBskyEmbedRecordWithMedia.Main',
'models.AppBskyEmbedVideo.Main'
]] = None,
langs: Optional[List[str]] = None,
facets: Optional[List['models.AppBskyRichtextFacet.Main']] = None
) -> models.AppBskyFeedPost.CreateRecordResponse:
"""
Create a post record.
Args:
text (str or TextBuilder): Post content
profile_identify (str, optional): Handle or DID where to send post
reply_to (AppBskyFeedPost.ReplyRef, optional): Root and parent of post to reply to
embed (optional): Embed models attached to the post
langs (List[str], optional): List of languages used in the post
facets (List[AppBskyRichtextFacet.Main], optional): Rich text facets
Returns:
AppBskyFeedPost.CreateRecordResponse: Reference to created record
"""
def get_timeline(self, **kwargs) -> models.AppBskyFeedGetTimeline.Response:
"""
Get the authenticated user's timeline.
Args:
**kwargs: Timeline parameters (limit, cursor, etc.)
Returns:
AppBskyFeedGetTimeline.Response: Timeline data
"""
def get_profile(self, actor: str) -> models.AppBskyActorGetProfile.Response:
"""
Get profile information for an actor.
Args:
actor (str): Handle or DID of the actor
Returns:
AppBskyActorGetProfile.Response: Profile information
"""
def delete_post(self, post_uri: str) -> bool:
"""
Delete a post record.
Args:
post_uri (str): URI of the post to delete
Returns:
bool: True if deletion was successful
"""
def send_image(self, text: str, image: bytes, image_alt: str, **kwargs) -> models.AppBskyFeedPost.CreateRecordResponse:
"""
Send a post with an image.
Args:
text (str): Post text content
image (bytes): Image data
image_alt (str): Alt text for the image
**kwargs: Additional post parameters
Returns:
AppBskyFeedPost.CreateRecordResponse: Created post record
"""
def get_post(self, post_rkey: str, profile_identify: str, cid: Optional[str] = None) -> models.ComAtprotoRepoGetRecord.Response:
"""
Get a single post record.
Args:
post_rkey (str): Record key of the post
profile_identify (str): Handle or DID of the post author
cid (str, optional): Specific version CID
Returns:
ComAtprotoRepoGetRecord.Response: Post record data
"""
def like(self, uri: str, cid: str) -> models.AppBskyFeedLike.CreateRecordResponse:
"""
Like a post.
Args:
uri (str): AT-URI of the post to like
cid (str): CID of the post to like
Returns:
AppBskyFeedLike.CreateRecordResponse: Like record reference
"""
def unlike(self, like_uri: str) -> bool:
"""
Remove a like from a post.
Args:
like_uri (str): URI of the like record to delete
Returns:
bool: True if unlike was successful
"""
def repost(self, uri: str, cid: str) -> models.AppBskyFeedRepost.CreateRecordResponse:
"""
Repost a post.
Args:
uri (str): AT-URI of the post to repost
cid (str): CID of the post to repost
Returns:
AppBskyFeedRepost.CreateRecordResponse: Repost record reference
"""
def follow(self, subject: str) -> models.AppBskyGraphFollow.CreateRecordResponse:
"""
Follow a user.
Args:
subject (str): Handle or DID of the user to follow
Returns:
AppBskyGraphFollow.CreateRecordResponse: Follow record reference
"""
def unfollow(self, follow_uri: str) -> bool:
"""
Unfollow a user.
Args:
follow_uri (str): URI of the follow record to delete
Returns:
bool: True if unfollow was successful
"""Usage example:
from atproto import Client
# Initialize and authenticate
client = Client()
session = client.login('alice.bsky.social', 'password')
# Access user profile
print(f"Logged in as: {client.me.handle}")
print(f"DID: {client.me.did}")
# Create a post
response = client.send_post(text="Hello AT Protocol!")
print(f"Post created: {response.uri}")
# Get timeline
timeline = client.get_timeline(limit=10)
for item in timeline.feed:
author = item.post.author
text = item.post.record.text
print(f"{author.handle}: {text}")The asynchronous version of the client with the same interface but async methods.
class AsyncClient:
"""
High-level asynchronous client for XRPC and ATProto operations.
Attributes:
me (Optional[models.AppBskyActorDefs.ProfileViewDetailed]): Current user profile
"""
def __init__(self, base_url: Optional[str] = None, *args, **kwargs):
"""
Initialize the async client.
Args:
base_url (str, optional): Custom base URL for AT Protocol server
"""
async def login(
self,
login: Optional[str] = None,
password: Optional[str] = None,
session_string: Optional[str] = None,
auth_factor_token: Optional[str] = None
) -> models.AppBskyActorDefs.ProfileViewDetailed:
"""
Authenticate with AT Protocol server asynchronously.
Args:
login (str, optional): Handle or email of the account
password (str, optional): Main or app-specific password
session_string (str, optional): Session string for re-authentication
auth_factor_token (str, optional): Auth factor token for Email 2FA
Note:
Either session_string or login and password should be provided.
Returns:
AppBskyActorDefs.ProfileViewDetailed: Profile information
"""
async def send_post(
self,
text: Union[str, 'client_utils.TextBuilder'],
profile_identify: Optional[str] = None,
reply_to: Optional['models.AppBskyFeedPost.ReplyRef'] = None,
embed: Optional[Union[
'models.AppBskyEmbedImages.Main',
'models.AppBskyEmbedExternal.Main',
'models.AppBskyEmbedRecord.Main',
'models.AppBskyEmbedRecordWithMedia.Main',
'models.AppBskyEmbedVideo.Main'
]] = None,
langs: Optional[List[str]] = None,
facets: Optional[List['models.AppBskyRichtextFacet.Main']] = None
) -> models.AppBskyFeedPost.CreateRecordResponse:
"""
Create a post record asynchronously.
Args:
text (str or TextBuilder): Post content
profile_identify (str, optional): Handle or DID where to send post
reply_to (AppBskyFeedPost.ReplyRef, optional): Root and parent of post to reply to
embed (optional): Embed models attached to the post
langs (List[str], optional): List of languages used in the post
facets (List[AppBskyRichtextFacet.Main], optional): Rich text facets
Returns:
AppBskyFeedPost.CreateRecordResponse: Reference to created record
"""
async def delete_post(self, post_uri: str) -> bool:
"""Delete a post record asynchronously."""
async def send_image(self, text: str, image: bytes, image_alt: str, **kwargs) -> models.AppBskyFeedPost.CreateRecordResponse:
"""Send a post with an image asynchronously."""
async def like(self, uri: str, cid: str) -> models.AppBskyFeedLike.CreateRecordResponse:
"""Like a post asynchronously."""
async def follow(self, subject: str) -> models.AppBskyGraphFollow.CreateRecordResponse:
"""Follow a user asynchronously."""
async def close(self):
"""Close the async HTTP client connection."""Usage example:
import asyncio
from atproto import AsyncClient
async def main():
client = AsyncClient()
# Authenticate
await client.login('alice.bsky.social', 'password')
# Create a post
await client.send_post(text="Hello from async!")
# Always close the client
await client.close()
asyncio.run(main())Represents an authenticated session with AT Protocol, including tokens and user information.
class Session:
"""
Authenticated session with AT Protocol.
Attributes:
handle (str): User handle
did (str): Decentralized identifier
access_jwt (str): Access token for API calls
refresh_jwt (str): Refresh token for renewing access
pds_endpoint (Optional[str]): Personal Data Server endpoint
"""
handle: str
did: str
access_jwt: str
refresh_jwt: str
pds_endpoint: Optional[str]
def encode(self) -> str:
"""
Serialize session to string for storage.
Returns:
str: Encoded session string
"""
@classmethod
def decode(cls, session_string: str) -> 'Session':
"""
Deserialize session from string.
Args:
session_string (str): Encoded session string
Returns:
Session: Decoded session object
"""
def copy(self) -> 'Session':
"""
Create a copy of the session.
Returns:
Session: Copied session object
"""
@property
def access_jwt_payload(self) -> 'JwtPayload':
"""Get decoded access token payload."""
@property
def refresh_jwt_payload(self) -> 'JwtPayload':
"""Get decoded refresh token payload."""Usage example:
from atproto import Client, Session
# Login and get session
client = Client()
session = client.login('alice.bsky.social', 'password')
# Save session for later use
session_string = session.encode()
# Store session_string securely...
# Restore session later
restored_session = Session.decode(session_string)
client = Client()
client.session = restored_sessionEvents that occur during session lifecycle for monitoring and debugging.
class SessionEvent(Enum):
"""Events during session lifecycle."""
IMPORT = 'import' # Session imported from storage
CREATE = 'create' # New session created via login
REFRESH = 'refresh' # Session refreshed with new tokensLow-level HTTP request handler using HTTPX for synchronous operations.
class Request:
"""
Synchronous HTTP request handler with HTTPX.
"""
def get(self, *args, **kwargs) -> 'Response':
"""
Make a GET request.
Returns:
Response: HTTP response wrapper
"""
def post(self, *args, **kwargs) -> 'Response':
"""
Make a POST request.
Returns:
Response: HTTP response wrapper
"""
def set_additional_headers(self, headers: Dict[str, str]):
"""
Set additional headers for all requests.
Args:
headers (Dict[str, str]): Headers to add
"""
def clone(self) -> 'Request':
"""
Create a cloned request handler.
Returns:
Request: Cloned request instance
"""Low-level HTTP request handler for asynchronous operations.
class AsyncRequest:
"""
Asynchronous HTTP request handler with HTTPX.
"""
async def get(self, *args, **kwargs) -> 'Response':
"""
Make an async GET request.
Returns:
Response: HTTP response wrapper
"""
async def post(self, *args, **kwargs) -> 'Response':
"""
Make an async POST request.
Returns:
Response: HTTP response wrapper
"""
async def close(self):
"""Close the async HTTP client."""HTTP response wrapper providing unified access to response data.
class Response:
"""
HTTP response wrapper.
Attributes:
success (bool): Whether the request was successful
status_code (int): HTTP status code
content (Optional[Union[Dict[str, Any], bytes, XrpcError]]): Response content
headers (Dict[str, Any]): Response headers
"""
success: bool
status_code: int
content: Optional[Union[Dict[str, Any], bytes, XrpcError]]
headers: Dict[str, Any]Helper for constructing rich text with facets (mentions, links, tags).
class TextBuilder:
"""
Helper for constructing rich text with facets.
"""
def __init__(self):
"""
Initialize text builder.
"""
def text(self, text: str) -> 'TextBuilder':
"""
Add plain text to the builder.
Args:
text (str): Text to add
Returns:
TextBuilder: Self for chaining
"""
def mention(self, text: str, did: str) -> 'TextBuilder':
"""
Add a mention facet.
Args:
text (str): Text of the mention (e.g., '@alice')
did (str): DID of the mentioned user
Returns:
TextBuilder: Self for chaining
"""
def link(self, text: str, url: str) -> 'TextBuilder':
"""
Add a link facet.
Args:
text (str): Link text
url (str): Target URL
Returns:
TextBuilder: Self for chaining
"""
def tag(self, text: str, tag: str) -> 'TextBuilder':
"""
Add a hashtag facet.
Args:
text (str): Text of the tag (e.g., '#atproto')
tag (str): Tag name (without #)
Returns:
TextBuilder: Self for chaining
"""
def build_text(self) -> str:
"""
Build the text from current state.
Returns:
str: Built text string
"""
def build_facets(self) -> List['models.AppBskyRichtextFacet.Main']:
"""
Build the facets from current state.
Returns:
List[models.AppBskyRichtextFacet.Main]: Built facets list
"""Usage example:
from atproto import client_utils
# Build rich text with mentions and links
builder = client_utils.TextBuilder()
rich_text = (builder
.text("Hello ")
.mention("@alice", "did:plc:alice123")
.text("! Check out ")
.link("this article", "https://example.com")
.text(" about ")
.tag("#atproto", "atproto"))
# Use in a post (TextBuilder can be passed directly)
client.send_post(rich_text)class ModelError(Exception):
"""Base exception for model-related errors."""
class NetworkError(Exception):
"""Network-related request errors."""
class UnauthorizedError(Exception):
"""Authentication/authorization errors."""
class LoginRequiredError(Exception):
"""Thrown when login is required for an operation."""Common error handling patterns:
from atproto import Client, LoginRequiredError, NetworkError
client = Client()
try:
client.login('user@example.com', 'wrong-password')
except UnauthorizedError:
print("Invalid credentials")
try:
# This requires authentication
client.send_post(text="Hello!")
except LoginRequiredError:
print("Please log in first")
except NetworkError as e:
print(f"Network error: {e}")Install with Tessl CLI
npx tessl i tessl/pypi-atproto