Skip to content

A python SDK for the ChangeCrab API

License

Notifications You must be signed in to change notification settings

changecrab/python_sdk

Repository files navigation

ChangeCrab Python SDK

The official Python client for the ChangeCrab API. Manage your changelogs and release notes programmatically.

CI

For complete API documentation, see the ChangeCrab API Overview.

Installation

pip install changecrab

Or install from source:

git clone https://github.com/changecrab/python_sdk.git
cd python_sdk
pip install .

Quick Start

from changecrab import ChangeCrab

# Initialize the client with your API key
client = ChangeCrab(api_key="your_api_key_here")

# List all your changelogs
changelogs = client.list_changelogs()
for changelog in changelogs:
    print(f"{changelog.name} ({changelog.access_id})")

# Create a new post
post = client.create_post(
    changelog_id="abc123def4",
    summary="🚀 New Feature: Dark Mode",
    markdown="We've added dark mode support! Toggle it in your settings.",
    team=1,
    public=True
)
print(f"Created post #{post.id}")

Authentication

Get your API key from your ChangeCrab account settings. All API requests require authentication.

For detailed authentication information, see the ChangeCrab API Overview.

from changecrab import ChangeCrab

client = ChangeCrab(api_key="your_api_key_here")

You can also configure the client with custom settings:

client = ChangeCrab(
    api_key="your_api_key_here",
    base_url="https://changecrab.com/api",  # Custom API URL (optional)
    timeout=60,  # Request timeout in seconds (default: 30)
    max_retries=3,  # Max retry attempts for transient errors (default: 3)
    retry_delay=1.0,  # Initial retry delay in seconds (default: 1.0, exponential backoff)
)

Retry Logic

The SDK automatically retries requests on transient errors (timeouts, connection errors) with exponential backoff:

  • Default: 3 retries with delays of 1s, 2s, 4s
  • Only retries on Timeout and ConnectionError exceptions
  • Non-retryable errors (authentication, validation, etc.) are raised immediately

Usage Guide

Changelogs

List Changelogs

Retrieve all changelogs accessible to your account:

# Get all changelogs
changelogs = client.list_changelogs()

# Filter by team ID
team_changelogs = client.list_changelogs(team_id=123)

Get a Changelog

Retrieve a specific changelog by its access ID:

changelog = client.get_changelog("abc123def4")
print(f"Name: {changelog.name}")
print(f"Subdomain: {changelog.subdomain}")
print(f"Created: {changelog.created_at}")

Create a Changelog

changelog = client.create_changelog(
    name="My Product Changelog",
    team=1,
    subdomain="myproduct",
    accent="#007bff",
    auto_notify=True,
    show_social=True
)
print(f"Created: {changelog.access_id}")

All available options:

Parameter Type Description
name str Required. Name of the changelog
team int Required. Team ID that owns the changelog
subdomain str Subdomain (e.g., "myapp" for myapp.changecrab.com)
domain str Custom domain
accent str Accent color in hex format (e.g., "#E33597")
private bool Whether the changelog is private
auto_notify bool Auto-notify subscribers of new posts
hide_subscriber bool Hide subscriber count
show_brand bool Show ChangeCrab branding
subscribe_active bool Enable subscriptions
return_url str Return URL for your site
slack_url str Slack webhook URL
teams_url str Microsoft Teams webhook URL
discord_webhook str Discord webhook URL
site_url str Your main website URL
twitter str Twitter handle
ga_tracking str Google Analytics tracking ID
logo str Logo URL
favicon str Favicon URL
extra_css str Custom CSS
show_creator bool Show post creator names
show_filters bool Show category filters
suggestion bool Enable suggestions
guest_voting bool Allow guest voting
guest_commenting bool Allow guest comments
guest_creation bool Allow guest suggestions
downvotes bool Enable downvoting

Update a Changelog

changelog = client.update_changelog(
    "abc123def4",
    name="Updated Changelog Name",
    accent="#ff5722",
    auto_notify=False
)

Delete a Changelog

client.delete_changelog("abc123def4")

Categories

Categories help organize posts within a changelog. Retrieve them to get category IDs for tagging posts.

categories = client.list_categories("abc123def4")
for cat in categories:
    print(f"{cat.title} (ID: {cat.id}, Color: {cat.colour})")

Posts

List Posts

posts = client.list_posts("abc123def4")
for post in posts:
    status = "Draft" if post.draft else "Published"
    print(f"[{status}] {post.summary}")

Create a Post

post = client.create_post(
    changelog_id="abc123def4",
    summary="Bug Fix: Login Issue Resolved",
    markdown="""
## What's Fixed

We've resolved an issue where some users couldn't log in using social authentication.

### Details
- Fixed OAuth callback handling
- Improved error messages for failed logins
- Added retry logic for transient failures

Thanks to everyone who reported this issue!
    """,
    team=1,
    public=True,
    announced=True,  # Notify subscribers
    categories=[1, 2]  # Category IDs from list_categories()
)

All available options:

Parameter Type Description
changelog_id str Required. Changelog access ID
summary str Required. Title/summary (max 2555 chars)
markdown str Required. Content in Markdown format
team int Required. Team ID
public bool Public visibility (default: True)
announced bool Notify subscribers (default: False)
draft bool Save as draft (default: False)
link str Related URL
record str Record identifier (single char)
categories list[int] Category IDs to tag the post

Update a Post

post = client.update_post(
    changelog_id="abc123def4",
    post_id=123,
    markdown="Updated content here with more details.",
    team=1,
    summary="Updated Title",
    announced=True,  # Announce to subscribers
    publish_date="2024-01-15",
    categories=[1, 3]
)

Delete a Post

client.delete_post("abc123def4", post_id=123)

Error Handling

The SDK provides specific exception classes for different error scenarios. All exceptions include status codes, request IDs (if available), and truncated response bodies for debugging.

Basic Error Handling

from changecrab import (
    ChangeCrab,
    ChangeCrabError,
    ApiError,  # Alias for ChangeCrabError
    AuthenticationError,
    NotFoundError,
    ValidationError,
    RateLimitError,
    ServerError,
)

client = ChangeCrab(api_key="your_api_key")

try:
    post = client.create_post(
        changelog_id="abc123def4",
        summary="",  # Empty summary will fail validation
        markdown="Content here",
        team=1
    )
except ValidationError as e:
    # Handle validation errors with field-level details
    print(f"Validation failed: {e.message}")
    print(f"Status: {e.status_code}")
    if e.request_id:
        print(f"Request ID: {e.request_id}")
    
    # Access field-level validation errors
    for field, messages in e.errors.items():
        print(f"  {field}: {', '.join(messages)}")
    
    # Response body is automatically truncated and sanitized
    # (no API keys or sensitive data leaked)
    if e.response_data:
        print(f"Response: {e.response_data}")

except AuthenticationError as e:
    # Invalid or missing API key
    print(f"Auth failed: {e.message} (status: {e.status_code})")
    if e.request_id:
        print(f"Request ID: {e.request_id}")

except NotFoundError as e:
    # Resource doesn't exist
    print(f"Not found: {e.message}")

except RateLimitError as e:
    # Rate limit exceeded - SDK automatically retries with backoff
    # This exception is only raised if all retries are exhausted
    print(f"Rate limited: {e.message}")
    print("The SDK automatically retried with exponential backoff")

except ServerError as e:
    # Server error - SDK automatically retries transient 5xx errors
    print(f"Server error: {e.message} (status: {e.status_code})")

except (ChangeCrabError, ApiError) as e:
    # Catch-all for any SDK error (ApiError is an alias)
    print(f"Error: {e.message}")
    print(f"Status: {e.status_code}")
    if e.request_id:
        print(f"Request ID: {e.request_id}")

Exception Properties

All exceptions provide the following properties:

  • message: Human-readable error message
  • status_code: HTTP status code (if available)
  • request_id: Request ID from API response headers (if available)
  • response_data: Sanitized and truncated response body (max 500 chars, API keys redacted)
  • errors: Field-level validation errors (ValidationError only)

Automatic Retries

The SDK automatically retries on:

  • Network errors: Timeouts and connection errors (exponential backoff)
  • HTTP 429 (Rate Limit): Respects Retry-After header if present
  • HTTP 5xx (Server Errors): 500, 502, 503, 504 (exponential backoff)

Retries are capped at max_retries (default: 3). Exceptions are only raised after all retries are exhausted.

Exception Hierarchy

ChangeCrabError (base) / ApiError (alias)
├── AuthenticationError  # 401, 403
├── NotFoundError        # 404
├── ValidationError      # 422 (includes field errors)
├── RateLimitError       # 429 (after retries exhausted)
└── ServerError          # 5xx (after retries exhausted)

Data Models

The SDK returns typed dataclass objects for easy access to resource properties:

Changelog

changelog = client.get_changelog("abc123def4")

# Access properties directly
print(changelog.id)           # Numeric ID
print(changelog.access_id)    # String ID used in API URLs
print(changelog.name)         # Display name
print(changelog.team)         # Team ID
print(changelog.subdomain)    # Subdomain
print(changelog.accent)       # Accent color
print(changelog.created_at)   # datetime object

# Convert to dictionary
data = changelog.to_dict()

Post

post = client.list_posts("abc123def4")[0]

print(post.id)           # Numeric ID
print(post.summary)      # Title
print(post.markdown)     # Content
print(post.public)       # bool
print(post.announced)    # bool
print(post.draft)        # bool
print(post.categories)   # List of PostCategory objects
print(post.created_at)   # datetime object

Category

category = client.list_categories("abc123def4")[0]

print(category.id)       # Numeric ID
print(category.title)    # Display name
print(category.colour)   # Hex color code

Environment Variables

You can set your API key as an environment variable:

export CHANGECRAB_API_KEY="your_api_key_here"

Then initialize the client:

import os
from changecrab import ChangeCrab

client = ChangeCrab(api_key=os.environ["CHANGECRAB_API_KEY"])

Type Hints

This SDK includes full type annotations and a py.typed marker for static type checkers like mypy:

from typing import Optional
from changecrab import ChangeCrab, Changelog, Post

def get_latest_post(client: ChangeCrab, changelog_id: str) -> Optional[Post]:
    posts = client.list_posts(changelog_id)
    return posts[0] if posts else None

Testing

The SDK includes a comprehensive test suite. To run tests:

# Install dev dependencies
pip install -e ".[dev]"

# Run all tests
pytest

# Run with coverage
pytest --cov=changecrab --cov-report=term

# Run specific test file
pytest tests/test_client.py

The test suite includes:

  • Unit tests for all client methods (with mocked API responses)
  • Model tests for data parsing and conversion
  • Exception handling tests
  • Error scenario tests

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/your-feature)
  3. Install dev dependencies (pip install -e ".[dev]")
  4. Make your changes
  5. Run tests (pytest)
  6. Run linting (ruff check .)
  7. Run type checking (mypy changecrab)
  8. Commit your changes
  9. Push to the branch
  10. Open a Pull Request

License

This project is licensed under the MIT License - see the LICENSE file for details.


Support

About

A python SDK for the ChangeCrab API

Resources

License

Stars

Watchers

Forks

Packages

No packages published