Skip to main content

Tiny ActivityPub framework written in Python, both database and server agnostic.

Project description

Active Boxes (Modernized Little Boxes)

This project is a fork of Little Boxes that has been modernized and relicensed from ISC to MIT.

⚠️ Modernization Complete, ActivityPub Compliance In Progress ⚠️

This project has been successfully modernized and updated to current Python packaging standards and Python 3.10+ features. Core ActivityPub functionality is implemented, with federation delivery features under development.

The original README can be found in ORIGINAL-README.md.

Current Status

  • Migrated from setup.py to pyproject.toml
  • Moved development dependencies to pyproject.toml
  • Switched to Poetry for dependency management and building
  • Updated to require Python 3.10+
  • Created comprehensive modernization plans
  • Modernized codebase to leverage Python 3.10+ features
  • Created comprehensive test suite
  • [~] ActivityPub protocol compliance - Core 11 activities ✅, Extended activities ⚠️
  • Updated documentation and examples
  • Prepared for stable release

Modernization Features

Python 3.10+ Features

  • Structural Pattern Matching (match/case statements)
  • Modern Union Types (X | Y syntax instead of Union[X, Y])
  • Parenthesized context managers
  • Improved type hinting throughout the codebase
  • Walrus operator usage where appropriate
  • Modern string formatting with f-strings

Code Quality

  • 100% type hinting coverage
  • Comprehensive test suite with ~89% coverage
  • Modern code formatting with Black
  • Strict linting with Ruff
  • Type checking with MyPy

Testing

  • ActivityPub protocol compliance testing (core activities)
  • Integration tests with mock servers
  • Property-based testing for robustness
  • Security-focused test suite (~89% coverage)

Implemented ActivityPub Features

Core Activities ✅

Create, Update, Delete, Follow, Accept, Reject, Add, Remove, Like, Block, Undo, Announce

Actor Properties ✅

inbox, outbox, following, followers, preferredUsername, endpoints (sharedInbox)

Collections ✅

Collection, OrderedCollection, CollectionPage, OrderedCollectionPage

Security ✅

HTTP Signatures (generation/verification), Linked Data Signatures

Plugin Interface ✅

active_boxes.plugin.ActivityPubPlugin - Protocol defining app responsibilities

Missing (Under Development)

  • Extended activities: Flag, Move, Join, Leave, View, Listen, Read, Write, Travel, Arrive
  • Per-object Likes/Shares collections
  • Backward pagination in collections

Quick Start

This is an async library - your plugin should use asyncio or any async framework (FastAPI, aiohttp, etc.).

1. Implement the Plugin Protocol

from active_boxes import activitypub as ap
from active_boxes.plugin import ActivityPubPlugin

class MyAppPlugin(ActivityPubPlugin):
    BASE_URL = "https://myapp.example"

    # Required: URL generation
    def base_url(self) -> str:
        return self.BASE_URL

    def activity_url(self, obj_id: str) -> str:
        return f"{self.BASE_URL}/activity/{obj_id}"

    def note_url(self, obj_id: str) -> str:
        return f"{self.BASE_URL}/note/{obj_id}"

    # Required: Deliver activities to remote inboxes
    async def deliver_activity(
        self,
        activity: dict,
        inbox: str,
        actor: dict,
    ) -> bool:
        signed = self.sign_request(activity, actor)
        async with httpx.AsyncClient() as client:
            resp = await client.post(inbox, json=signed)
        return resp.status_code in (200, 201, 202)

    # Required: Process incoming activities
    async def receive_activity(
        self,
        activity: dict,
        source_inbox: str | None = None,
    ) -> bool:
        if self.is_duplicate(activity["id"]):
            return False  # Skip duplicate
        await self.store_activity(activity, source_inbox)
        await self.process_activity(activity)
        return True

    # Required: Deduplication
    def is_duplicate(self, activity_id: str) -> bool:
        return self.redis.exists(f"activity:{activity_id}")

    # Optional: Add extra recipients for all activities
    def extra_inboxes(self) -> list[str]:
        return []  # Or add a shared inbox

    def sign_request(self, activity: dict, actor: dict) -> dict:
        # Your HTTP signature logic here
        ...

2. Initialize the Backend

from active_boxes import activitypub as ap

plugin = MyAppPlugin()
ap.use_backend(plugin)

3. Create and Send Activities

# Create a note
note = ap.Note(
    content="Hello, federation!",
    attributedTo="https://myapp.example/user/alice",
    to=[ap.AS_PUBLIC],
)

# Create the activity wrapping the note
create = note.build_create()
create.set_id("https://myapp.example/activity/abc123", "abc123")

# Get recipients and deliver
recipients = create.recipients()  # Computed by library
for inbox in recipients:
    actor = fetch_actor(create.get_actor().id)
    await plugin.deliver_activity(create.to_dict(), inbox, actor)

4. Receive Activities

# In your inbox endpoint handler
async def inbox_handler(request):
    activity = await request.json()
    await plugin.receive_activity(activity, source_inbox=str(request.url))
    return web.Response(status=202)

5. Working with Actors

# Create a person actor
person = ap.Person(
    id="https://myapp.example/user/alice",
    inbox="https://myapp.example/user/alice/inbox",
    outbox="https://myapp.example/user/alice/outbox",
    followers="https://myapp.example/user/alice/followers",
    preferredUsername="alice",
    publicKey={
        "id": "https://myapp.example/user/alice#main-key",
        "owner": "https://myapp.example/user/alice",
        "publicKeyPem": "-----BEGIN PUBLIC KEY-----...",
    },
)

6. Collection Pagination

# Build a paginated outbox
outbox = ap.OrderedCollection(
    id="https://myapp.example/user/alice/outbox",
    totalItems=42,
    first="https://myapp.example/user/alice/outbox?page=1",
)

# Library handles parsing remote collections
items = backend.parse_collection(url="https://example.com/user/bob/outbox")

Plugin Responsibilities

What Library Does What Your App Does
Activity/Object serialization HTTP client setup (httpx, aiohttp, etc.)
Computing recipients Signing outgoing requests (HTTP Signatures)
HTTP Signature generation Delivering to remote inboxes
HTTP Signature verification Receiving from remote inboxes
Collection pagination Storing activities persistently
Activity vocabulary (Create, Follow, etc.) Deduplication
WebFinger support Retry/backoff logic

Modernization Plans

Detailed planning documents have been created to guide the modernization effort:

Original Project

For information about the original project, please refer to ORIGINAL-README.md.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

active_boxes-0.1.0.tar.gz (23.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

active_boxes-0.1.0-py3-none-any.whl (26.3 kB view details)

Uploaded Python 3

File details

Details for the file active_boxes-0.1.0.tar.gz.

File metadata

  • Download URL: active_boxes-0.1.0.tar.gz
  • Upload date:
  • Size: 23.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.3.2 CPython/3.14.3 Linux/6.19.8-200.fc43.x86_64

File hashes

Hashes for active_boxes-0.1.0.tar.gz
Algorithm Hash digest
SHA256 2e1da27983b223a8f547672067a1be56ee4ee482165c7117ee4c0607d20fe47c
MD5 33b2ce61019ea31aa1d69303ccdc6cec
BLAKE2b-256 26379562d141dabe6423731f8807b2edecf8e1439e199de8c9a94029715e5cb3

See more details on using hashes here.

File details

Details for the file active_boxes-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: active_boxes-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 26.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: poetry/2.3.2 CPython/3.14.3 Linux/6.19.8-200.fc43.x86_64

File hashes

Hashes for active_boxes-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 66f4614090ab2d0328cb3e6fe98315084fa28366bbc16e065bfe346705a23174
MD5 a448c1864b2607738e1be6ba45bf3715
BLAKE2b-256 bb5da4f3805eba8b022527993ffcfc0a3f8be9fc3880514073b22ac627125649

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page