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
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:
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.