active-boxes 0.0.1.dev2__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (28) hide show
  1. {active_boxes-0.0.1.dev2 → active_boxes-0.2.0}/LICENSE +2 -2
  2. active_boxes-0.2.0/PKG-INFO +323 -0
  3. active_boxes-0.2.0/README.md +294 -0
  4. {active_boxes-0.0.1.dev2 → active_boxes-0.2.0}/active_boxes/__version__.py +1 -1
  5. active_boxes-0.2.0/active_boxes/activitypub.py +1637 -0
  6. active_boxes-0.2.0/active_boxes/backend.py +252 -0
  7. active_boxes-0.2.0/active_boxes/collection.py +396 -0
  8. {active_boxes-0.0.1.dev2 → active_boxes-0.2.0}/active_boxes/content_helper.py +21 -19
  9. active_boxes-0.2.0/active_boxes/data_integrity.py +214 -0
  10. {active_boxes-0.0.1.dev2 → active_boxes-0.2.0}/active_boxes/errors.py +4 -5
  11. active_boxes-0.2.0/active_boxes/http_client.py +549 -0
  12. active_boxes-0.2.0/active_boxes/httpsig.py +933 -0
  13. active_boxes-0.2.0/active_boxes/key.py +205 -0
  14. active_boxes-0.2.0/active_boxes/linked_data_sig.py +129 -0
  15. active_boxes-0.2.0/active_boxes/plugin.py +359 -0
  16. {active_boxes-0.0.1.dev2 → active_boxes-0.2.0}/active_boxes/urlutils.py +19 -9
  17. active_boxes-0.2.0/active_boxes/webfinger.py +172 -0
  18. {active_boxes-0.0.1.dev2 → active_boxes-0.2.0}/pyproject.toml +32 -33
  19. active_boxes-0.0.1.dev2/PKG-INFO +0 -45
  20. active_boxes-0.0.1.dev2/README.md +0 -19
  21. active_boxes-0.0.1.dev2/active_boxes/activitypub.py +0 -984
  22. active_boxes-0.0.1.dev2/active_boxes/backend.py +0 -128
  23. active_boxes-0.0.1.dev2/active_boxes/collection.py +0 -70
  24. active_boxes-0.0.1.dev2/active_boxes/httpsig.py +0 -145
  25. active_boxes-0.0.1.dev2/active_boxes/key.py +0 -63
  26. active_boxes-0.0.1.dev2/active_boxes/linked_data_sig.py +0 -83
  27. active_boxes-0.0.1.dev2/active_boxes/webfinger.py +0 -92
  28. {active_boxes-0.0.1.dev2 → active_boxes-0.2.0}/active_boxes/__init__.py +0 -0
@@ -1,7 +1,7 @@
1
1
  MIT License
2
2
 
3
3
  Copyright (c) 2018, Thomas Sileo
4
- Copyright (c) 2025, Chaiwat Suttipongsakul
4
+ Copyright (c) 2025-2026, Chaiwat Suttipongsakul
5
5
 
6
6
  Permission is hereby granted, free of charge, to any person obtaining a copy
7
7
  of this software and associated documentation files (the "Software"), to deal
@@ -19,4 +19,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
19
  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
20
  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
21
  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR PERFORMANCE OF THE
22
- SOFTWARE.
22
+ SOFTWARE.
@@ -0,0 +1,323 @@
1
+ Metadata-Version: 2.4
2
+ Name: active-boxes
3
+ Version: 0.2.0
4
+ Summary: Tiny ActivityPub framework written in Python, both database and server agnostic.
5
+ License: MIT
6
+ License-File: LICENSE
7
+ Author: Chaiwat Suttipongsakul
8
+ Author-email: cwt@bashell.com
9
+ Requires-Python: >=3.10
10
+ Classifier: Development Status :: 2 - Pre-Alpha
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: Implementation :: CPython
17
+ Requires-Dist: aiohttp (>=3.9.0)
18
+ Requires-Dist: bleach (>=6.0.0)
19
+ Requires-Dist: html2text (>=2020.1.16)
20
+ Requires-Dist: markdown (>=3.4.0)
21
+ Requires-Dist: mdx_linkify (>=1.5.0)
22
+ Requires-Dist: pycryptodome (>=3.18.0)
23
+ Requires-Dist: pyld (>=2.0.0)
24
+ Requires-Dist: regex (>=2023.0.0)
25
+ Requires-Dist: requests (>=2.31.0)
26
+ Project-URL: Homepage, https://github.com/cwt/active-boxes
27
+ Description-Content-Type: text/markdown
28
+
29
+ # Active Boxes (Modernized Little Boxes)
30
+
31
+ This project is a fork of [Little Boxes](https://github.com/tsileo/little-boxes) that has been modernized and relicensed from ISC to MIT.
32
+
33
+ **Modernization Complete, ActivityPub Compliance In Progress**
34
+
35
+ 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.
36
+
37
+ The original README can be found in [ORIGINAL-README.md](ORIGINAL-README.md).
38
+
39
+ ## Current Status
40
+
41
+ - [x] Migrated from `setup.py` to `pyproject.toml`
42
+ - [x] Moved development dependencies to `pyproject.toml`
43
+ - [x] Switched to Poetry for dependency management and building
44
+ - [x] Updated to require Python 3.10+
45
+ - [x] Created comprehensive modernization plans
46
+ - [x] Modernized codebase to leverage Python 3.10+ features
47
+ - [x] Created comprehensive test suite
48
+ - [x] ActivityPub protocol compliance - Core 11 activities, Extended activities
49
+ - [x] Updated documentation and examples
50
+ - [x] Prepared for stable release
51
+ - [x] **Async-by-default API** with sync wrappers for Flask/Django compatibility
52
+
53
+ ## Modernization Features
54
+
55
+ ### Python 3.10+ Features
56
+
57
+ - Structural Pattern Matching (match/case statements)
58
+ - Modern Union Types (`X | Y` syntax instead of `Union[X, Y]`)
59
+ - Parenthesized context managers
60
+ - Improved type hinting throughout the codebase
61
+ - Walrus operator usage where appropriate
62
+ - Modern string formatting with f-strings
63
+
64
+ ### Code Quality
65
+
66
+ - 100% type hinting coverage
67
+ - Comprehensive test suite with ~89% code coverage
68
+ - Modern code formatting with Black
69
+ - Strict linting with Ruff
70
+ - Type checking with MyPy
71
+
72
+ ### Testing
73
+
74
+ - ActivityPub protocol compliance testing (core + extended activities)
75
+ - Integration tests with mock servers
76
+ - Property-based testing for robustness
77
+ - Security-focused test suite (~89% coverage)
78
+
79
+ ## Implemented ActivityPub Features
80
+
81
+ ### Core Activities [x]
82
+
83
+ Create, Update, Delete, Follow, Accept, Reject, Add, Remove, Like, Block, Undo, Announce
84
+
85
+ ### Extended Activities [x]
86
+
87
+ Flag, Move, Join, Leave, View, Listen, Read, Write, Travel, Arrive
88
+
89
+ ### Actor Properties [x]
90
+
91
+ inbox, outbox, following, followers, preferredUsername, endpoints (sharedInbox)
92
+
93
+ ### Collections [x]
94
+
95
+ Collection, OrderedCollection, CollectionPage, OrderedCollectionPage
96
+
97
+ ### Security [x]
98
+
99
+ HTTP Signatures (generation/verification), Linked Data Signatures
100
+
101
+ ### Plugin Interface [x]
102
+
103
+ `active_boxes.plugin.ActivityPubPlugin` - Protocol defining app responsibilities
104
+
105
+ ### Missing (Under Development)
106
+
107
+ - Per-object Likes/Shares collections
108
+ - Backward pagination in collections
109
+ - Featured collection support
110
+
111
+ ## Quick Start
112
+
113
+ **This is an async-first library** - the primary API uses `async`/`await`. Sync wrappers (e.g., `fetch_iri_sync()`) are available for Flask/Django compatibility.
114
+
115
+ ### 1. Implement the Plugin Protocol
116
+
117
+ ```python
118
+ from active_boxes import activitypub as ap
119
+ from active_boxes.plugin import ActivityPubPlugin
120
+
121
+ class MyAppPlugin(ActivityPubPlugin):
122
+ BASE_URL = "https://myapp.example"
123
+
124
+ # Required: URL generation
125
+ def base_url(self) -> str:
126
+ return self.BASE_URL
127
+
128
+ def activity_url(self, obj_id: str) -> str:
129
+ return f"{self.BASE_URL}/activity/{obj_id}"
130
+
131
+ def note_url(self, obj_id: str) -> str:
132
+ return f"{self.BASE_URL}/note/{obj_id}"
133
+
134
+ # Required: Deliver activities to remote inboxes
135
+ async def deliver_activity(
136
+ self,
137
+ activity: dict,
138
+ inbox: str,
139
+ actor: dict,
140
+ ) -> bool:
141
+ signed = self.sign_request(activity, actor)
142
+ async with httpx.AsyncClient() as client:
143
+ resp = await client.post(inbox, json=signed)
144
+ return resp.status_code in (200, 201, 202)
145
+
146
+ # Required: Process incoming activities
147
+ async def receive_activity(
148
+ self,
149
+ activity: dict,
150
+ source_inbox: str | None = None,
151
+ ) -> bool:
152
+ if self.is_duplicate(activity["id"]):
153
+ return False # Skip duplicate
154
+ await self.store_activity(activity, source_inbox)
155
+ await self.process_activity(activity)
156
+ return True
157
+
158
+ # Required: Deduplication
159
+ def is_duplicate(self, activity_id: str) -> bool:
160
+ return self.redis.exists(f"activity:{activity_id}")
161
+
162
+ # Optional: Add extra recipients for all activities
163
+ def extra_inboxes(self) -> list[str]:
164
+ return [] # Or add a shared inbox
165
+
166
+ def sign_request(self, activity: dict, actor: dict) -> dict:
167
+ # Your HTTP signature logic here
168
+ ...
169
+ ```
170
+
171
+ ### 2. Initialize the Backend
172
+
173
+ ```python
174
+ from active_boxes import activitypub as ap
175
+
176
+ plugin = MyAppPlugin()
177
+ ap.use_backend(plugin)
178
+ ```
179
+
180
+ ### 3. Create and Send Activities
181
+
182
+ **Async (Recommended for FastAPI, aiohttp, etc.):**
183
+
184
+ ```python
185
+ # Create a note
186
+ note = ap.Note(
187
+ content="Hello, federation!",
188
+ attributedTo="https://myapp.example/user/alice",
189
+ to=[ap.AS_PUBLIC],
190
+ )
191
+
192
+ # Create the activity wrapping the note
193
+ create = note.build_create()
194
+ create.set_id("https://myapp.example/activity/abc123", "abc123")
195
+
196
+ # Get recipients and deliver
197
+ recipients = create.recipients() # Computed by library
198
+ for inbox in recipients:
199
+ actor = await fetch_actor(create.get_actor().id)
200
+ await plugin.deliver_activity(create.to_dict(), inbox, actor)
201
+ ```
202
+
203
+ **Sync (For Flask, Django sync views):**
204
+
205
+ ```python
206
+ # Create a note
207
+ note = ap.Note(
208
+ content="Hello, federation!",
209
+ attributedTo="https://myapp.example/user/alice",
210
+ to=[ap.AS_PUBLIC],
211
+ )
212
+
213
+ # Create the activity wrapping the note
214
+ create = note.build_create()
215
+ create.set_id("https://myapp.example/activity/abc123", "abc123")
216
+
217
+ # Get recipients and deliver (sync wrapper)
218
+ recipients = create.recipients()
219
+ for inbox in recipients:
220
+ actor = fetch_actor_sync(create.get_actor_sync().id)
221
+ plugin.deliver_activity(create.to_dict(), inbox, actor)
222
+ ```
223
+
224
+ ### 4. Receive Activities
225
+
226
+ **Async (FastAPI, aiohttp):**
227
+
228
+ ```python
229
+ # In your inbox endpoint handler
230
+ async def inbox_handler(request):
231
+ activity = await request.json()
232
+ await plugin.receive_activity(activity, source_inbox=str(request.url))
233
+ return web.Response(status=202)
234
+ ```
235
+
236
+ **Sync (Flask, Django sync views):**
237
+
238
+ ```python
239
+ # In your Flask route
240
+ @app.post("/inbox")
241
+ def inbox():
242
+ activity = request.get_json()
243
+ plugin.receive_activity_sync(activity, source_inbox=request.url)
244
+ return "", 202
245
+ ```
246
+
247
+ ### 5. Working with Actors
248
+
249
+ ```python
250
+ # Create a person actor
251
+ person = ap.Person(
252
+ id="https://myapp.example/user/alice",
253
+ inbox="https://myapp.example/user/alice/inbox",
254
+ outbox="https://myapp.example/user/alice/outbox",
255
+ followers="https://myapp.example/user/alice/followers",
256
+ preferredUsername="alice",
257
+ publicKey={
258
+ "id": "https://myapp.example/user/alice#main-key",
259
+ "owner": "https://myapp.example/user/alice",
260
+ "publicKeyPem": "-----BEGIN PUBLIC KEY-----...",
261
+ },
262
+ )
263
+ ```
264
+
265
+ ### 6. Collection Pagination
266
+
267
+ ```python
268
+ # Build a paginated outbox
269
+ outbox = ap.OrderedCollection(
270
+ id="https://myapp.example/user/alice/outbox",
271
+ totalItems=42,
272
+ first="https://myapp.example/user/alice/outbox?page=1",
273
+ )
274
+
275
+ # Library handles parsing remote collections (async)
276
+ items = await backend.parse_collection(url="https://example.com/user/bob/outbox")
277
+
278
+ # Or use sync wrapper
279
+ items = backend.parse_collection_sync(url="https://example.com/user/bob/outbox")
280
+ ```
281
+
282
+ ## API Naming Convention
283
+
284
+ The library uses an **async-first** naming convention:
285
+
286
+ | Operation | Async (Primary) | Sync Wrapper |
287
+ |-----------|----------------|--------------|
288
+ | Fetch IRI | `fetch_iri()` | `fetch_iri_sync()` |
289
+ | Fetch JSON | `fetch_json()` | `fetch_json_sync()` |
290
+ | Get Actor | `get_actor()` | `get_actor_sync()` |
291
+ | Get Object | `get_object()` | `get_object_sync()` |
292
+ | WebFinger | `webfinger()` | `webfinger_sync()` |
293
+ | Verify Signature | `verify_request()` | `verify_request_sync()` |
294
+ | Parse Collection | `parse_collection()` | `parse_collection_sync()` |
295
+
296
+ **Guideline:** Use async methods by default. Use `_sync()` variants only when integrating with sync frameworks like Flask or Django sync views.
297
+
298
+ ## Plugin Responsibilities
299
+
300
+ | What Library Does | What Your App Does |
301
+ |-------------------|-------------------|
302
+ | Activity/Object serialization | HTTP client setup (httpx, aiohttp, etc.) |
303
+ | Computing recipients | Signing outgoing requests (HTTP Signatures) |
304
+ | HTTP Signature generation | Delivering to remote inboxes |
305
+ | HTTP Signature verification | Receiving from remote inboxes |
306
+ | Collection pagination | Storing activities persistently |
307
+ | Activity vocabulary (Create, Follow, etc.) | Deduplication |
308
+ | WebFinger support | Retry/backoff logic |
309
+
310
+ ## Modernization Plans
311
+
312
+ Detailed planning documents have been created to guide the modernization effort:
313
+
314
+ - [MODERNIZE_PLAN.md](documents/MODERNIZE_PLAN.md) - Overall modernization strategy
315
+ - [PYTHON_310_MODERNIZATION.md](documents/PYTHON_310_MODERNIZATION.md) - Python 3.10+ feature implementation
316
+ - [TEST_SUITE_IMPROVEMENTS.md](documents/TEST_SUITE_IMPROVEMENTS.md) - Test suite enhancement plans
317
+ - [ACTIVITYPUB_COMPLIANCE.md](documents/ACTIVITYPUB_COMPLIANCE.md) - ActivityPub protocol compliance requirements
318
+ - [IMPLEMENTATION_PLAN.md](documents/IMPLEMENTATION_PLAN.md) - Detailed 8-week implementation timeline
319
+
320
+ ## Original Project
321
+
322
+ For information about the original project, please refer to [ORIGINAL-README.md](ORIGINAL-README.md).
323
+
@@ -0,0 +1,294 @@
1
+ # Active Boxes (Modernized Little Boxes)
2
+
3
+ This project is a fork of [Little Boxes](https://github.com/tsileo/little-boxes) that has been modernized and relicensed from ISC to MIT.
4
+
5
+ **Modernization Complete, ActivityPub Compliance In Progress**
6
+
7
+ 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.
8
+
9
+ The original README can be found in [ORIGINAL-README.md](ORIGINAL-README.md).
10
+
11
+ ## Current Status
12
+
13
+ - [x] Migrated from `setup.py` to `pyproject.toml`
14
+ - [x] Moved development dependencies to `pyproject.toml`
15
+ - [x] Switched to Poetry for dependency management and building
16
+ - [x] Updated to require Python 3.10+
17
+ - [x] Created comprehensive modernization plans
18
+ - [x] Modernized codebase to leverage Python 3.10+ features
19
+ - [x] Created comprehensive test suite
20
+ - [x] ActivityPub protocol compliance - Core 11 activities, Extended activities
21
+ - [x] Updated documentation and examples
22
+ - [x] Prepared for stable release
23
+ - [x] **Async-by-default API** with sync wrappers for Flask/Django compatibility
24
+
25
+ ## Modernization Features
26
+
27
+ ### Python 3.10+ Features
28
+
29
+ - Structural Pattern Matching (match/case statements)
30
+ - Modern Union Types (`X | Y` syntax instead of `Union[X, Y]`)
31
+ - Parenthesized context managers
32
+ - Improved type hinting throughout the codebase
33
+ - Walrus operator usage where appropriate
34
+ - Modern string formatting with f-strings
35
+
36
+ ### Code Quality
37
+
38
+ - 100% type hinting coverage
39
+ - Comprehensive test suite with ~89% code coverage
40
+ - Modern code formatting with Black
41
+ - Strict linting with Ruff
42
+ - Type checking with MyPy
43
+
44
+ ### Testing
45
+
46
+ - ActivityPub protocol compliance testing (core + extended activities)
47
+ - Integration tests with mock servers
48
+ - Property-based testing for robustness
49
+ - Security-focused test suite (~89% coverage)
50
+
51
+ ## Implemented ActivityPub Features
52
+
53
+ ### Core Activities [x]
54
+
55
+ Create, Update, Delete, Follow, Accept, Reject, Add, Remove, Like, Block, Undo, Announce
56
+
57
+ ### Extended Activities [x]
58
+
59
+ Flag, Move, Join, Leave, View, Listen, Read, Write, Travel, Arrive
60
+
61
+ ### Actor Properties [x]
62
+
63
+ inbox, outbox, following, followers, preferredUsername, endpoints (sharedInbox)
64
+
65
+ ### Collections [x]
66
+
67
+ Collection, OrderedCollection, CollectionPage, OrderedCollectionPage
68
+
69
+ ### Security [x]
70
+
71
+ HTTP Signatures (generation/verification), Linked Data Signatures
72
+
73
+ ### Plugin Interface [x]
74
+
75
+ `active_boxes.plugin.ActivityPubPlugin` - Protocol defining app responsibilities
76
+
77
+ ### Missing (Under Development)
78
+
79
+ - Per-object Likes/Shares collections
80
+ - Backward pagination in collections
81
+ - Featured collection support
82
+
83
+ ## Quick Start
84
+
85
+ **This is an async-first library** - the primary API uses `async`/`await`. Sync wrappers (e.g., `fetch_iri_sync()`) are available for Flask/Django compatibility.
86
+
87
+ ### 1. Implement the Plugin Protocol
88
+
89
+ ```python
90
+ from active_boxes import activitypub as ap
91
+ from active_boxes.plugin import ActivityPubPlugin
92
+
93
+ class MyAppPlugin(ActivityPubPlugin):
94
+ BASE_URL = "https://myapp.example"
95
+
96
+ # Required: URL generation
97
+ def base_url(self) -> str:
98
+ return self.BASE_URL
99
+
100
+ def activity_url(self, obj_id: str) -> str:
101
+ return f"{self.BASE_URL}/activity/{obj_id}"
102
+
103
+ def note_url(self, obj_id: str) -> str:
104
+ return f"{self.BASE_URL}/note/{obj_id}"
105
+
106
+ # Required: Deliver activities to remote inboxes
107
+ async def deliver_activity(
108
+ self,
109
+ activity: dict,
110
+ inbox: str,
111
+ actor: dict,
112
+ ) -> bool:
113
+ signed = self.sign_request(activity, actor)
114
+ async with httpx.AsyncClient() as client:
115
+ resp = await client.post(inbox, json=signed)
116
+ return resp.status_code in (200, 201, 202)
117
+
118
+ # Required: Process incoming activities
119
+ async def receive_activity(
120
+ self,
121
+ activity: dict,
122
+ source_inbox: str | None = None,
123
+ ) -> bool:
124
+ if self.is_duplicate(activity["id"]):
125
+ return False # Skip duplicate
126
+ await self.store_activity(activity, source_inbox)
127
+ await self.process_activity(activity)
128
+ return True
129
+
130
+ # Required: Deduplication
131
+ def is_duplicate(self, activity_id: str) -> bool:
132
+ return self.redis.exists(f"activity:{activity_id}")
133
+
134
+ # Optional: Add extra recipients for all activities
135
+ def extra_inboxes(self) -> list[str]:
136
+ return [] # Or add a shared inbox
137
+
138
+ def sign_request(self, activity: dict, actor: dict) -> dict:
139
+ # Your HTTP signature logic here
140
+ ...
141
+ ```
142
+
143
+ ### 2. Initialize the Backend
144
+
145
+ ```python
146
+ from active_boxes import activitypub as ap
147
+
148
+ plugin = MyAppPlugin()
149
+ ap.use_backend(plugin)
150
+ ```
151
+
152
+ ### 3. Create and Send Activities
153
+
154
+ **Async (Recommended for FastAPI, aiohttp, etc.):**
155
+
156
+ ```python
157
+ # Create a note
158
+ note = ap.Note(
159
+ content="Hello, federation!",
160
+ attributedTo="https://myapp.example/user/alice",
161
+ to=[ap.AS_PUBLIC],
162
+ )
163
+
164
+ # Create the activity wrapping the note
165
+ create = note.build_create()
166
+ create.set_id("https://myapp.example/activity/abc123", "abc123")
167
+
168
+ # Get recipients and deliver
169
+ recipients = create.recipients() # Computed by library
170
+ for inbox in recipients:
171
+ actor = await fetch_actor(create.get_actor().id)
172
+ await plugin.deliver_activity(create.to_dict(), inbox, actor)
173
+ ```
174
+
175
+ **Sync (For Flask, Django sync views):**
176
+
177
+ ```python
178
+ # Create a note
179
+ note = ap.Note(
180
+ content="Hello, federation!",
181
+ attributedTo="https://myapp.example/user/alice",
182
+ to=[ap.AS_PUBLIC],
183
+ )
184
+
185
+ # Create the activity wrapping the note
186
+ create = note.build_create()
187
+ create.set_id("https://myapp.example/activity/abc123", "abc123")
188
+
189
+ # Get recipients and deliver (sync wrapper)
190
+ recipients = create.recipients()
191
+ for inbox in recipients:
192
+ actor = fetch_actor_sync(create.get_actor_sync().id)
193
+ plugin.deliver_activity(create.to_dict(), inbox, actor)
194
+ ```
195
+
196
+ ### 4. Receive Activities
197
+
198
+ **Async (FastAPI, aiohttp):**
199
+
200
+ ```python
201
+ # In your inbox endpoint handler
202
+ async def inbox_handler(request):
203
+ activity = await request.json()
204
+ await plugin.receive_activity(activity, source_inbox=str(request.url))
205
+ return web.Response(status=202)
206
+ ```
207
+
208
+ **Sync (Flask, Django sync views):**
209
+
210
+ ```python
211
+ # In your Flask route
212
+ @app.post("/inbox")
213
+ def inbox():
214
+ activity = request.get_json()
215
+ plugin.receive_activity_sync(activity, source_inbox=request.url)
216
+ return "", 202
217
+ ```
218
+
219
+ ### 5. Working with Actors
220
+
221
+ ```python
222
+ # Create a person actor
223
+ person = ap.Person(
224
+ id="https://myapp.example/user/alice",
225
+ inbox="https://myapp.example/user/alice/inbox",
226
+ outbox="https://myapp.example/user/alice/outbox",
227
+ followers="https://myapp.example/user/alice/followers",
228
+ preferredUsername="alice",
229
+ publicKey={
230
+ "id": "https://myapp.example/user/alice#main-key",
231
+ "owner": "https://myapp.example/user/alice",
232
+ "publicKeyPem": "-----BEGIN PUBLIC KEY-----...",
233
+ },
234
+ )
235
+ ```
236
+
237
+ ### 6. Collection Pagination
238
+
239
+ ```python
240
+ # Build a paginated outbox
241
+ outbox = ap.OrderedCollection(
242
+ id="https://myapp.example/user/alice/outbox",
243
+ totalItems=42,
244
+ first="https://myapp.example/user/alice/outbox?page=1",
245
+ )
246
+
247
+ # Library handles parsing remote collections (async)
248
+ items = await backend.parse_collection(url="https://example.com/user/bob/outbox")
249
+
250
+ # Or use sync wrapper
251
+ items = backend.parse_collection_sync(url="https://example.com/user/bob/outbox")
252
+ ```
253
+
254
+ ## API Naming Convention
255
+
256
+ The library uses an **async-first** naming convention:
257
+
258
+ | Operation | Async (Primary) | Sync Wrapper |
259
+ |-----------|----------------|--------------|
260
+ | Fetch IRI | `fetch_iri()` | `fetch_iri_sync()` |
261
+ | Fetch JSON | `fetch_json()` | `fetch_json_sync()` |
262
+ | Get Actor | `get_actor()` | `get_actor_sync()` |
263
+ | Get Object | `get_object()` | `get_object_sync()` |
264
+ | WebFinger | `webfinger()` | `webfinger_sync()` |
265
+ | Verify Signature | `verify_request()` | `verify_request_sync()` |
266
+ | Parse Collection | `parse_collection()` | `parse_collection_sync()` |
267
+
268
+ **Guideline:** Use async methods by default. Use `_sync()` variants only when integrating with sync frameworks like Flask or Django sync views.
269
+
270
+ ## Plugin Responsibilities
271
+
272
+ | What Library Does | What Your App Does |
273
+ |-------------------|-------------------|
274
+ | Activity/Object serialization | HTTP client setup (httpx, aiohttp, etc.) |
275
+ | Computing recipients | Signing outgoing requests (HTTP Signatures) |
276
+ | HTTP Signature generation | Delivering to remote inboxes |
277
+ | HTTP Signature verification | Receiving from remote inboxes |
278
+ | Collection pagination | Storing activities persistently |
279
+ | Activity vocabulary (Create, Follow, etc.) | Deduplication |
280
+ | WebFinger support | Retry/backoff logic |
281
+
282
+ ## Modernization Plans
283
+
284
+ Detailed planning documents have been created to guide the modernization effort:
285
+
286
+ - [MODERNIZE_PLAN.md](documents/MODERNIZE_PLAN.md) - Overall modernization strategy
287
+ - [PYTHON_310_MODERNIZATION.md](documents/PYTHON_310_MODERNIZATION.md) - Python 3.10+ feature implementation
288
+ - [TEST_SUITE_IMPROVEMENTS.md](documents/TEST_SUITE_IMPROVEMENTS.md) - Test suite enhancement plans
289
+ - [ACTIVITYPUB_COMPLIANCE.md](documents/ACTIVITYPUB_COMPLIANCE.md) - ActivityPub protocol compliance requirements
290
+ - [IMPLEMENTATION_PLAN.md](documents/IMPLEMENTATION_PLAN.md) - Detailed 8-week implementation timeline
291
+
292
+ ## Original Project
293
+
294
+ For information about the original project, please refer to [ORIGINAL-README.md](ORIGINAL-README.md).
@@ -2,7 +2,7 @@ try:
2
2
  import importlib.metadata as importlib_metadata
3
3
  except ImportError:
4
4
  # Python < 3.8
5
- import importlib_metadata
5
+ import importlib_metadata # type: ignore[no-redef,import-not-found]
6
6
 
7
7
  try:
8
8
  __version__ = importlib_metadata.version("active-boxes")