active-boxes 0.0.1.dev2__tar.gz → 0.1.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.
@@ -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,262 @@
1
+ Metadata-Version: 2.4
2
+ Name: active-boxes
3
+ Version: 0.1.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: bleach (>=6.0.0)
18
+ Requires-Dist: html2text (>=2020.1.16)
19
+ Requires-Dist: markdown (>=3.4.0)
20
+ Requires-Dist: mdx_linkify (>=1.5.0)
21
+ Requires-Dist: pycryptodome (>=3.18.0)
22
+ Requires-Dist: pyld (>=2.0.0)
23
+ Requires-Dist: regex (>=2023.0.0)
24
+ Requires-Dist: requests (>=2.31.0)
25
+ Project-URL: Homepage, https://github.com/cwt/active-boxes
26
+ Description-Content-Type: text/markdown
27
+
28
+ # Active Boxes (Modernized Little Boxes)
29
+
30
+ This project is a fork of [Little Boxes](https://github.com/tsileo/little-boxes) that has been modernized and relicensed from ISC to MIT.
31
+
32
+ ⚠️ **Modernization Complete, ActivityPub Compliance In Progress** ⚠️
33
+
34
+ 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.
35
+
36
+ The original README can be found in [ORIGINAL-README.md](ORIGINAL-README.md).
37
+
38
+ ## Current Status
39
+
40
+ - [x] Migrated from `setup.py` to `pyproject.toml`
41
+ - [x] Moved development dependencies to `pyproject.toml`
42
+ - [x] Switched to Poetry for dependency management and building
43
+ - [x] Updated to require Python 3.10+
44
+ - [x] Created comprehensive modernization plans
45
+ - [x] Modernized codebase to leverage Python 3.10+ features
46
+ - [x] Created comprehensive test suite
47
+ - [~] ActivityPub protocol compliance - Core 11 activities ✅, Extended activities ⚠️
48
+ - [x] Updated documentation and examples
49
+ - [x] Prepared for stable release
50
+
51
+ ## Modernization Features
52
+
53
+ ### Python 3.10+ Features
54
+
55
+ - Structural Pattern Matching (match/case statements)
56
+ - Modern Union Types (`X | Y` syntax instead of `Union[X, Y]`)
57
+ - Parenthesized context managers
58
+ - Improved type hinting throughout the codebase
59
+ - Walrus operator usage where appropriate
60
+ - Modern string formatting with f-strings
61
+
62
+ ### Code Quality
63
+
64
+ - 100% type hinting coverage
65
+ - Comprehensive test suite with ~89% coverage
66
+ - Modern code formatting with Black
67
+ - Strict linting with Ruff
68
+ - Type checking with MyPy
69
+
70
+ ### Testing
71
+
72
+ - ActivityPub protocol compliance testing (core activities)
73
+ - Integration tests with mock servers
74
+ - Property-based testing for robustness
75
+ - Security-focused test suite (~89% coverage)
76
+
77
+ ## Implemented ActivityPub Features
78
+
79
+ ### Core Activities ✅
80
+
81
+ Create, Update, Delete, Follow, Accept, Reject, Add, Remove, Like, Block, Undo, Announce
82
+
83
+ ### Actor Properties ✅
84
+
85
+ inbox, outbox, following, followers, preferredUsername, endpoints (sharedInbox)
86
+
87
+ ### Collections ✅
88
+
89
+ Collection, OrderedCollection, CollectionPage, OrderedCollectionPage
90
+
91
+ ### Security ✅
92
+
93
+ HTTP Signatures (generation/verification), Linked Data Signatures
94
+
95
+ ### Plugin Interface ✅
96
+
97
+ `active_boxes.plugin.ActivityPubPlugin` - Protocol defining app responsibilities
98
+
99
+ ### Missing (Under Development)
100
+
101
+ - Extended activities: Flag, Move, Join, Leave, View, Listen, Read, Write, Travel, Arrive
102
+ - Per-object Likes/Shares collections
103
+ - Backward pagination in collections
104
+
105
+ ## Quick Start
106
+
107
+ **This is an async library** - your plugin should use `asyncio` or any async framework (FastAPI, aiohttp, etc.).
108
+
109
+ ### 1. Implement the Plugin Protocol
110
+
111
+ ```python
112
+ from active_boxes import activitypub as ap
113
+ from active_boxes.plugin import ActivityPubPlugin
114
+
115
+ class MyAppPlugin(ActivityPubPlugin):
116
+ BASE_URL = "https://myapp.example"
117
+
118
+ # Required: URL generation
119
+ def base_url(self) -> str:
120
+ return self.BASE_URL
121
+
122
+ def activity_url(self, obj_id: str) -> str:
123
+ return f"{self.BASE_URL}/activity/{obj_id}"
124
+
125
+ def note_url(self, obj_id: str) -> str:
126
+ return f"{self.BASE_URL}/note/{obj_id}"
127
+
128
+ # Required: Deliver activities to remote inboxes
129
+ async def deliver_activity(
130
+ self,
131
+ activity: dict,
132
+ inbox: str,
133
+ actor: dict,
134
+ ) -> bool:
135
+ signed = self.sign_request(activity, actor)
136
+ async with httpx.AsyncClient() as client:
137
+ resp = await client.post(inbox, json=signed)
138
+ return resp.status_code in (200, 201, 202)
139
+
140
+ # Required: Process incoming activities
141
+ async def receive_activity(
142
+ self,
143
+ activity: dict,
144
+ source_inbox: str | None = None,
145
+ ) -> bool:
146
+ if self.is_duplicate(activity["id"]):
147
+ return False # Skip duplicate
148
+ await self.store_activity(activity, source_inbox)
149
+ await self.process_activity(activity)
150
+ return True
151
+
152
+ # Required: Deduplication
153
+ def is_duplicate(self, activity_id: str) -> bool:
154
+ return self.redis.exists(f"activity:{activity_id}")
155
+
156
+ # Optional: Add extra recipients for all activities
157
+ def extra_inboxes(self) -> list[str]:
158
+ return [] # Or add a shared inbox
159
+
160
+ def sign_request(self, activity: dict, actor: dict) -> dict:
161
+ # Your HTTP signature logic here
162
+ ...
163
+ ```
164
+
165
+ ### 2. Initialize the Backend
166
+
167
+ ```python
168
+ from active_boxes import activitypub as ap
169
+
170
+ plugin = MyAppPlugin()
171
+ ap.use_backend(plugin)
172
+ ```
173
+
174
+ ### 3. Create and Send Activities
175
+
176
+ ```python
177
+ # Create a note
178
+ note = ap.Note(
179
+ content="Hello, federation!",
180
+ attributedTo="https://myapp.example/user/alice",
181
+ to=[ap.AS_PUBLIC],
182
+ )
183
+
184
+ # Create the activity wrapping the note
185
+ create = note.build_create()
186
+ create.set_id("https://myapp.example/activity/abc123", "abc123")
187
+
188
+ # Get recipients and deliver
189
+ recipients = create.recipients() # Computed by library
190
+ for inbox in recipients:
191
+ actor = fetch_actor(create.get_actor().id)
192
+ await plugin.deliver_activity(create.to_dict(), inbox, actor)
193
+ ```
194
+
195
+ ### 4. Receive Activities
196
+
197
+ ```python
198
+ # In your inbox endpoint handler
199
+ async def inbox_handler(request):
200
+ activity = await request.json()
201
+ await plugin.receive_activity(activity, source_inbox=str(request.url))
202
+ return web.Response(status=202)
203
+ ```
204
+
205
+ ### 5. Working with Actors
206
+
207
+ ```python
208
+ # Create a person actor
209
+ person = ap.Person(
210
+ id="https://myapp.example/user/alice",
211
+ inbox="https://myapp.example/user/alice/inbox",
212
+ outbox="https://myapp.example/user/alice/outbox",
213
+ followers="https://myapp.example/user/alice/followers",
214
+ preferredUsername="alice",
215
+ publicKey={
216
+ "id": "https://myapp.example/user/alice#main-key",
217
+ "owner": "https://myapp.example/user/alice",
218
+ "publicKeyPem": "-----BEGIN PUBLIC KEY-----...",
219
+ },
220
+ )
221
+ ```
222
+
223
+ ### 6. Collection Pagination
224
+
225
+ ```python
226
+ # Build a paginated outbox
227
+ outbox = ap.OrderedCollection(
228
+ id="https://myapp.example/user/alice/outbox",
229
+ totalItems=42,
230
+ first="https://myapp.example/user/alice/outbox?page=1",
231
+ )
232
+
233
+ # Library handles parsing remote collections
234
+ items = backend.parse_collection(url="https://example.com/user/bob/outbox")
235
+ ```
236
+
237
+ ## Plugin Responsibilities
238
+
239
+ | What Library Does | What Your App Does |
240
+ |-------------------|-------------------|
241
+ | Activity/Object serialization | HTTP client setup (httpx, aiohttp, etc.) |
242
+ | Computing recipients | Signing outgoing requests (HTTP Signatures) |
243
+ | HTTP Signature generation | Delivering to remote inboxes |
244
+ | HTTP Signature verification | Receiving from remote inboxes |
245
+ | Collection pagination | Storing activities persistently |
246
+ | Activity vocabulary (Create, Follow, etc.) | Deduplication |
247
+ | WebFinger support | Retry/backoff logic |
248
+
249
+ ## Modernization Plans
250
+
251
+ Detailed planning documents have been created to guide the modernization effort:
252
+
253
+ - [MODERNIZE_PLAN.md](documents/MODERNIZE_PLAN.md) - Overall modernization strategy
254
+ - [PYTHON_310_MODERNIZATION.md](documents/PYTHON_310_MODERNIZATION.md) - Python 3.10+ feature implementation
255
+ - [TEST_SUITE_IMPROVEMENTS.md](documents/TEST_SUITE_IMPROVEMENTS.md) - Test suite enhancement plans
256
+ - [ACTIVITYPUB_COMPLIANCE.md](documents/ACTIVITYPUB_COMPLIANCE.md) - ActivityPub protocol compliance requirements
257
+ - [IMPLEMENTATION_PLAN.md](documents/IMPLEMENTATION_PLAN.md) - Detailed 8-week implementation timeline
258
+
259
+ ## Original Project
260
+
261
+ For information about the original project, please refer to [ORIGINAL-README.md](ORIGINAL-README.md).
262
+
@@ -0,0 +1,234 @@
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
+ - [~] ActivityPub protocol compliance - Core 11 activities ✅, Extended activities ⚠️
21
+ - [x] Updated documentation and examples
22
+ - [x] Prepared for stable release
23
+
24
+ ## Modernization Features
25
+
26
+ ### Python 3.10+ Features
27
+
28
+ - Structural Pattern Matching (match/case statements)
29
+ - Modern Union Types (`X | Y` syntax instead of `Union[X, Y]`)
30
+ - Parenthesized context managers
31
+ - Improved type hinting throughout the codebase
32
+ - Walrus operator usage where appropriate
33
+ - Modern string formatting with f-strings
34
+
35
+ ### Code Quality
36
+
37
+ - 100% type hinting coverage
38
+ - Comprehensive test suite with ~89% coverage
39
+ - Modern code formatting with Black
40
+ - Strict linting with Ruff
41
+ - Type checking with MyPy
42
+
43
+ ### Testing
44
+
45
+ - ActivityPub protocol compliance testing (core activities)
46
+ - Integration tests with mock servers
47
+ - Property-based testing for robustness
48
+ - Security-focused test suite (~89% coverage)
49
+
50
+ ## Implemented ActivityPub Features
51
+
52
+ ### Core Activities ✅
53
+
54
+ Create, Update, Delete, Follow, Accept, Reject, Add, Remove, Like, Block, Undo, Announce
55
+
56
+ ### Actor Properties ✅
57
+
58
+ inbox, outbox, following, followers, preferredUsername, endpoints (sharedInbox)
59
+
60
+ ### Collections ✅
61
+
62
+ Collection, OrderedCollection, CollectionPage, OrderedCollectionPage
63
+
64
+ ### Security ✅
65
+
66
+ HTTP Signatures (generation/verification), Linked Data Signatures
67
+
68
+ ### Plugin Interface ✅
69
+
70
+ `active_boxes.plugin.ActivityPubPlugin` - Protocol defining app responsibilities
71
+
72
+ ### Missing (Under Development)
73
+
74
+ - Extended activities: Flag, Move, Join, Leave, View, Listen, Read, Write, Travel, Arrive
75
+ - Per-object Likes/Shares collections
76
+ - Backward pagination in collections
77
+
78
+ ## Quick Start
79
+
80
+ **This is an async library** - your plugin should use `asyncio` or any async framework (FastAPI, aiohttp, etc.).
81
+
82
+ ### 1. Implement the Plugin Protocol
83
+
84
+ ```python
85
+ from active_boxes import activitypub as ap
86
+ from active_boxes.plugin import ActivityPubPlugin
87
+
88
+ class MyAppPlugin(ActivityPubPlugin):
89
+ BASE_URL = "https://myapp.example"
90
+
91
+ # Required: URL generation
92
+ def base_url(self) -> str:
93
+ return self.BASE_URL
94
+
95
+ def activity_url(self, obj_id: str) -> str:
96
+ return f"{self.BASE_URL}/activity/{obj_id}"
97
+
98
+ def note_url(self, obj_id: str) -> str:
99
+ return f"{self.BASE_URL}/note/{obj_id}"
100
+
101
+ # Required: Deliver activities to remote inboxes
102
+ async def deliver_activity(
103
+ self,
104
+ activity: dict,
105
+ inbox: str,
106
+ actor: dict,
107
+ ) -> bool:
108
+ signed = self.sign_request(activity, actor)
109
+ async with httpx.AsyncClient() as client:
110
+ resp = await client.post(inbox, json=signed)
111
+ return resp.status_code in (200, 201, 202)
112
+
113
+ # Required: Process incoming activities
114
+ async def receive_activity(
115
+ self,
116
+ activity: dict,
117
+ source_inbox: str | None = None,
118
+ ) -> bool:
119
+ if self.is_duplicate(activity["id"]):
120
+ return False # Skip duplicate
121
+ await self.store_activity(activity, source_inbox)
122
+ await self.process_activity(activity)
123
+ return True
124
+
125
+ # Required: Deduplication
126
+ def is_duplicate(self, activity_id: str) -> bool:
127
+ return self.redis.exists(f"activity:{activity_id}")
128
+
129
+ # Optional: Add extra recipients for all activities
130
+ def extra_inboxes(self) -> list[str]:
131
+ return [] # Or add a shared inbox
132
+
133
+ def sign_request(self, activity: dict, actor: dict) -> dict:
134
+ # Your HTTP signature logic here
135
+ ...
136
+ ```
137
+
138
+ ### 2. Initialize the Backend
139
+
140
+ ```python
141
+ from active_boxes import activitypub as ap
142
+
143
+ plugin = MyAppPlugin()
144
+ ap.use_backend(plugin)
145
+ ```
146
+
147
+ ### 3. Create and Send Activities
148
+
149
+ ```python
150
+ # Create a note
151
+ note = ap.Note(
152
+ content="Hello, federation!",
153
+ attributedTo="https://myapp.example/user/alice",
154
+ to=[ap.AS_PUBLIC],
155
+ )
156
+
157
+ # Create the activity wrapping the note
158
+ create = note.build_create()
159
+ create.set_id("https://myapp.example/activity/abc123", "abc123")
160
+
161
+ # Get recipients and deliver
162
+ recipients = create.recipients() # Computed by library
163
+ for inbox in recipients:
164
+ actor = fetch_actor(create.get_actor().id)
165
+ await plugin.deliver_activity(create.to_dict(), inbox, actor)
166
+ ```
167
+
168
+ ### 4. Receive Activities
169
+
170
+ ```python
171
+ # In your inbox endpoint handler
172
+ async def inbox_handler(request):
173
+ activity = await request.json()
174
+ await plugin.receive_activity(activity, source_inbox=str(request.url))
175
+ return web.Response(status=202)
176
+ ```
177
+
178
+ ### 5. Working with Actors
179
+
180
+ ```python
181
+ # Create a person actor
182
+ person = ap.Person(
183
+ id="https://myapp.example/user/alice",
184
+ inbox="https://myapp.example/user/alice/inbox",
185
+ outbox="https://myapp.example/user/alice/outbox",
186
+ followers="https://myapp.example/user/alice/followers",
187
+ preferredUsername="alice",
188
+ publicKey={
189
+ "id": "https://myapp.example/user/alice#main-key",
190
+ "owner": "https://myapp.example/user/alice",
191
+ "publicKeyPem": "-----BEGIN PUBLIC KEY-----...",
192
+ },
193
+ )
194
+ ```
195
+
196
+ ### 6. Collection Pagination
197
+
198
+ ```python
199
+ # Build a paginated outbox
200
+ outbox = ap.OrderedCollection(
201
+ id="https://myapp.example/user/alice/outbox",
202
+ totalItems=42,
203
+ first="https://myapp.example/user/alice/outbox?page=1",
204
+ )
205
+
206
+ # Library handles parsing remote collections
207
+ items = backend.parse_collection(url="https://example.com/user/bob/outbox")
208
+ ```
209
+
210
+ ## Plugin Responsibilities
211
+
212
+ | What Library Does | What Your App Does |
213
+ |-------------------|-------------------|
214
+ | Activity/Object serialization | HTTP client setup (httpx, aiohttp, etc.) |
215
+ | Computing recipients | Signing outgoing requests (HTTP Signatures) |
216
+ | HTTP Signature generation | Delivering to remote inboxes |
217
+ | HTTP Signature verification | Receiving from remote inboxes |
218
+ | Collection pagination | Storing activities persistently |
219
+ | Activity vocabulary (Create, Follow, etc.) | Deduplication |
220
+ | WebFinger support | Retry/backoff logic |
221
+
222
+ ## Modernization Plans
223
+
224
+ Detailed planning documents have been created to guide the modernization effort:
225
+
226
+ - [MODERNIZE_PLAN.md](documents/MODERNIZE_PLAN.md) - Overall modernization strategy
227
+ - [PYTHON_310_MODERNIZATION.md](documents/PYTHON_310_MODERNIZATION.md) - Python 3.10+ feature implementation
228
+ - [TEST_SUITE_IMPROVEMENTS.md](documents/TEST_SUITE_IMPROVEMENTS.md) - Test suite enhancement plans
229
+ - [ACTIVITYPUB_COMPLIANCE.md](documents/ACTIVITYPUB_COMPLIANCE.md) - ActivityPub protocol compliance requirements
230
+ - [IMPLEMENTATION_PLAN.md](documents/IMPLEMENTATION_PLAN.md) - Detailed 8-week implementation timeline
231
+
232
+ ## Original Project
233
+
234
+ 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")