Skip to content

Upgrading

This page covers migration from minimal_activitypub, the upstream package that longwei was forked from.


Upgrading from minimal_activitypub to longwei

longwei is a fork of minimal_activitypub. The public API is largely the same, but there are several breaking changes to address when migrating.

Breaking changes at a glance

What minimal_activitypub longwei
Package name minimal_activitypub longwei
Main class name ActivityPub APClient
Main import from minimal_activitypub.client_2_server import ActivityPub from longwei import APClient
Enum imports from minimal_activitypub import Visibility, SearchType from longwei import Visibility, SearchType
Exception imports from minimal_activitypub import NetworkError, ... from longwei import NetworkError, ...
Model imports from minimal_activitypub.models import Status from longwei import Status or from longwei.models import Status
Recommended instantiation ActivityPub(…) then await ap.determine_instance_type() await APClient.create(…)
Field access on Status status["id"] status.id
Optional field access status.get("content") status.content (may be None)
verify_credentials return type dict Account
Field access on Account account["username"] account.username
Status at runtime isinstance(status, dict)True isinstance(status, dict)False
delete_status(status=…) arg type str \| dict str \| Status
undo_reblog(status=…) arg type str \| dict str \| Status
undo_favourite(status=…) arg type str \| dict str \| Status
New public export Account from longwei

Migration steps

1. Replace the package name in dependencies

pip uninstall minimal_activitypub
pip install longwei

2. Update imports

# Before
from minimal_activitypub.client_2_server import ActivityPub
from minimal_activitypub import Visibility, SearchType, NetworkError

# After
from longwei import APClient, Visibility, SearchType, NetworkError

3. Rename class references

Replace all uses of ActivityPub (as a class or type annotation) with APClient.

4. Switch to the async factory

APClient.create() is the recommended entry point. It calls determine_instance_type() internally, so server-specific parameters (status length limit, attachment limits, MIME types) are configured before any API call is made.

# Before
ap = ActivityPub(instance="https://mastodon.social", client=client, access_token="token")
await ap.determine_instance_type()

# After
ap = await APClient.create(instance="https://mastodon.social", client=client, access_token="token")

Direct instantiation via APClient(…) still works and is not removed, but it does not auto-detect the instance type. The instance_type attribute defaults to INSTANCE_TYPE_MASTODON until detection runs.

5. Switch from dict access to attribute access

Status and Account objects are Pydantic BaseModel instances, not plain dicts. All fields use attribute access and default to None when absent.

# Before — dict-style
status_id = status["id"]
content   = status.get("content", "")
account   = status.get("account", {})
username  = account.get("username")

# After — attribute access
status_id = status.id
content   = status.content or ""
account   = status.account
username  = account.username if account else None

Guard optional fields with is not None before use. Replace any "field" in status key-presence checks:

# Before
if "reblog" in status and status["reblog"]:
    ...

# After
if status.reblog is not None:
    ...

6. Update verify_credentials usage

verify_credentials now returns an Account object instead of a dict:

# Before
user = await ap.verify_credentials()
print(user["username"])
print(user.get("display_name", ""))

# After
user = await ap.verify_credentials()
print(user.username)
print(user.display_name or "")

7. Pass Status objects (not dicts) to delete/undo methods

delete_status, undo_reblog, and undo_favourite accept str | Status, not str | dict. In practice this only matters if you construct status objects manually — objects returned by API calls are already Status instances and require no change:

# This pattern is unchanged
statuses = await ap.get_public_timeline()
await ap.delete_status(status=statuses[0])

If you do construct a status manually, validate it first:

from longwei.models import Status
status_obj = Status.model_validate({"id": "123", "reblogged": True, "reblog": {"id": "456"}})
await ap.delete_status(status=status_obj)

Nested types

Nested objects that were previously untyped dict are now Pydantic models:

Model Access via
Account status.account
MediaAttachment status.media_attachments[n]
Mention status.mentions[n]
Tag status.tags[n]
Emoji status.emojis[n] or account.emojis[n]
Application status.application
Card status.card
Poll / PollOption status.poll, status.poll.options[n]
Field account.fields[n]
# Before
media_url = status["media_attachments"][0]["url"]
tag_name  = status["tags"][0]["name"]

# After
media_url = status.media_attachments[0].url if status.media_attachments else None
tag_name  = status.tags[0].name if status.tags else None

Unknown fields from server responses are silently accepted (extra="allow") and accessible via model.model_extra:

# Pleroma-specific field (not in the Status model definition)
pleroma_data = status.model_extra.get("pleroma")

Complete before/after example

# ---- Before (minimal_activitypub) ----
from minimal_activitypub.client_2_server import ActivityPub
import httpx

async with httpx.AsyncClient() as client:
    ap = ActivityPub(instance="https://mastodon.social", client=client, access_token="token")
    await ap.determine_instance_type()

    user = await ap.verify_credentials()
    print(f"@{user['username']}")

    timeline = await ap.get_public_timeline(limit=5)
    for status in timeline:
        author  = status.get("account", {}).get("username", "?")
        content = status.get("content", "")[:80]
        print(f"@{author}: {content}")

    posted = await ap.post_status("Hello fediverse!")
    print(f"Posted: {posted['url']}")


# ---- After (longwei) ----
from longwei import APClient
import httpx

async with httpx.AsyncClient() as client:
    ap = await APClient.create(instance="https://mastodon.social", client=client,
                               access_token="token")

    user = await ap.verify_credentials()
    print(f"@{user.username}")

    timeline = await ap.get_public_timeline(limit=5)
    for status in timeline:
        author  = status.account.username if status.account else "?"
        content = (status.content or "")[:80]
        print(f"@{author}: {content}")

    posted = await ap.post_status("Hello fediverse!")
    print(f"Posted: {posted.url}")

Note on internal imports

Do not import from internal modules (anything prefixed _mixin_). Use the public API via from longwei import ... only.