Skip to content

Commit 40fbffa

Browse files
authored
Merge pull request #77 from fedi-libs/chore/pyrefly-to-ty
feat: migrate pyrefly to ty
2 parents d38ea70 + bf912bb commit 40fbffa

35 files changed

Lines changed: 1225 additions & 1673 deletions

.gitignore

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,4 +209,10 @@ __marimo__/
209209
private_key*.pem
210210

211211
devel/
212-
data/
212+
_version.py
213+
214+
/*.py
215+
data/
216+
217+
# auto-generated file
218+
src/apkit/_version.py

.zed/settings.json

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,10 @@
11
{
22
"languages": {
33
"Python": {
4-
"language_servers": ["ruff", "pyrefly"]
4+
"language_servers": ["ruff", "ty"]
55
}
66
},
77
"lsp": {
8-
"pyrefly": {
9-
"binary": {
10-
"path": ".venv/bin/pyrefly",
11-
"arguments": ["lsp"]
12-
},
13-
"settings": {
14-
"python": {
15-
"pythonPath": ".venv/bin/python"
16-
},
17-
"pyrefly": {
18-
"project_includes": ["src/**/*.py", "tests/**/*.py"],
19-
"project_excludes": ["**/.[!/.]*", "**/*venv/**"],
20-
"search_path": ["src"],
21-
"ignore_errors_in_generated_code": true
22-
}
23-
}
24-
},
258
"ruff": {
269
"initialization_options": {
2710
"settings": {

examples/delete.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from cryptography.hazmat.primitives import serialization as crypto_serialization
99
from cryptography.hazmat.primitives.asymmetric import rsa
1010

11-
from apkit.client.asyncio import ActivityPubClient
11+
from apkit.client import ActivityPubClient
1212
from apkit.models import CryptographicKey, Delete, Person
1313

1414
if len(sys.argv) < 3:
@@ -104,9 +104,12 @@ async def delete_note(recepient: str, object_id: str) -> None:
104104
# Deliver the activity
105105
logger.info("Delivering activity...")
106106

107+
if not actor.public_key or not actor.public_key.id:
108+
raise ValueError("public_key.id not found")
109+
107110
resp = await client.post(
108111
inbox_url,
109-
key_id=actor.publicKey.id,
112+
key_id=actor.public_key.id,
110113
signature=private_key,
111114
json=delete,
112115
)

examples/follow.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
from cryptography.hazmat.primitives import serialization as crypto_serialization
88
from cryptography.hazmat.primitives.asymmetric import rsa
99

10-
from apkit.client.asyncio import ActivityPubClient
11-
from apkit.client.models import Resource as WebfingerResource
10+
from apkit.client import ActivityPubClient
11+
from apmodel.webfinger import Resource as WebfingerResource
1212
from apkit.models import CryptographicKey, Follow, Person
1313

1414
if len(sys.argv) < 2:
@@ -70,10 +70,10 @@
7070
summary="This is a demo actor powered by apkit!",
7171
inbox=f"https://{HOST}/users/{USER_ID}/inbox",
7272
outbox=f"https://{HOST}/users/{USER_ID}/outbox",
73-
public_key_pem=CryptographicKey(
73+
public_key=CryptographicKey(
7474
id=f"https://{HOST}/users/{USER_ID}#main-key",
7575
owner=f"https://{HOST}/users/{USER_ID}",
76-
public_key=public_key_pem,
76+
public_key_pem=public_key_pem,
7777
),
7878
)
7979

@@ -113,6 +113,9 @@ async def follow(actor_id: str) -> None:
113113
# Deliver the activity
114114
logger.info("Delivering activity...")
115115

116+
if not actor.public_key or not actor.public_key.id:
117+
raise ValueError("public_key.id not found")
118+
116119
resp = await client.post(
117120
inbox_url,
118121
key_id=actor.public_key.id,

examples/like.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
import uuid
66
from datetime import UTC, datetime
77

8+
from apmodel.objects import Actor
89
from cryptography.hazmat.primitives import serialization as crypto_serialization
9-
from cryptography.hazmat.primitives.asymmetric import rsa
10+
from cryptography.hazmat.primitives.asymmetric import ed25519, rsa
1011

11-
from apkit.client.asyncio import ActivityPubClient
12+
from apkit.client import ActivityPubClient
1213
from apkit.models import CryptographicKey, Like, Person
14+
from apkit.types import ActorKey
1315

1416
if len(sys.argv) < 2:
1517
print("USAGE: python like.py <OBJECT_ID>", file=sys.stderr)
@@ -89,6 +91,9 @@ async def like(object_id: str) -> None:
8991
logger.info(f"Author of the object is: {sender_id}")
9092

9193
target_actor = await client.actor.fetch(sender_id)
94+
if not isinstance(target_actor, Actor):
95+
raise ValueError("Actor not found.")
96+
9297
# Get the inbox URL from the actor's profile
9398
inbox_url = target_actor.inbox
9499
if not inbox_url:
@@ -109,10 +114,17 @@ async def like(object_id: str) -> None:
109114
# Deliver the activity
110115
logger.info("Delivering activity...")
111116

117+
if not actor.public_key or not actor.public_key.id:
118+
raise ValueError("Actor's publickey is missing")
119+
120+
if not isinstance(private_key, rsa.RSAPrivateKey) and not isinstance(
121+
private_key, ed25519.Ed25519PrivateKey
122+
):
123+
raise ValueError("Invalid Key")
124+
112125
resp = await client.post(
113126
inbox_url,
114-
key_id=actor.publicKey.id,
115-
signature=private_key,
127+
signatures=[ActorKey(key_id=actor.public_key.id, private_key=private_key)],
116128
json=activity,
117129
)
118130
logger.info(f"Delivery result: {resp.status}")

examples/minimal_server.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,19 @@
22
import logging
33
import os
44
import sys
5+
import uuid
56

67
from cryptography.hazmat.primitives import serialization as crypto_serialization
7-
from cryptography.hazmat.primitives.asymmetric import rsa
8+
from cryptography.hazmat.primitives.asymmetric import ed25519, rsa
89
from fastapi import Request, Response
910
from fastapi.responses import JSONResponse
1011

11-
from apkit.client import WebfingerLink, WebfingerResource, WebfingerResult
12-
from apkit.client.asyncio.client import ActivityPubClient
12+
from apkit.client import (
13+
ActivityPubClient,
14+
WebfingerLink,
15+
WebfingerResource,
16+
WebfingerResult,
17+
)
1318
from apkit.models import (
1419
Actor as APKitActor,
1520
)
@@ -83,26 +88,33 @@
8388
actor = Person(
8489
id=f"https://{HOST}/users/{USER_ID}",
8590
name="apkit Demo",
86-
preferredUsername="demo",
91+
preferred_username="demo",
8792
summary="This is a demo actor powered by apkit!",
8893
inbox=f"https://{HOST}/users/{USER_ID}/inbox",
8994
outbox=f"https://{HOST}/users/{USER_ID}/outbox",
90-
publicKey=CryptographicKey(
95+
public_key=CryptographicKey(
9196
id=f"https://{HOST}/users/{USER_ID}#main-key",
9297
owner=f"https://{HOST}/users/{USER_ID}",
93-
publicKeyPem=public_key_pem,
98+
public_key_pem=public_key_pem,
9499
),
95100
)
96101

102+
97103
# --- Server Initialization ---
98104
app = ActivityPubServer()
99105

100106

101107
# --- Key Retrieval Function ---
102108
# This function provides the private key for signing outgoing activities.
103109
def get_keys_for_actor(identifier: str) -> list[ActorKey]:
104-
if identifier == USER_ID:
105-
return [ActorKey(key_id=actor.publicKey.id, private_key=private_key)]
110+
if not actor.public_key:
111+
raise ValueError("PublicKey not found.")
112+
if not isinstance(private_key, rsa.RSAPrivateKey) and not isinstance(
113+
private_key, ed25519.Ed25519PrivateKey
114+
):
115+
raise ValueError("Invalid Key")
116+
if identifier == USER_ID and actor.public_key.id:
117+
return [ActorKey(key_id=actor.public_key.id, private_key=private_key)]
106118
return []
107119

108120

@@ -177,7 +189,7 @@ async def outbox(ctx: Context):
177189

178190
if not ctx.request.query_params.get("page"):
179191
outbox = OrderedCollection()
180-
outbox.totalItems = 0 # No letter in the mail today.
192+
outbox.total_items = 0 # No letter in the mail today.
181193
outbox.id = f"https://{HOST}/users/{identifier}/outbox"
182194
outbox.first = f"{outbox.id}?page=true"
183195
outbox.last = f"{outbox.id}?min_id=0&page=true"
@@ -202,15 +214,15 @@ async def on_follow_activity(ctx: Context) -> Response:
202214
elif isinstance(activity.actor, APKitActor):
203215
follower_actor = activity.actor
204216

205-
if not follower_actor:
217+
if not follower_actor or not isinstance(follower_actor, APKitActor):
206218
return JSONResponse(
207219
{"error": "Could not resolve follower actor"}, status_code=400
208220
)
209221

210222
logger.info(f"🫂 {follower_actor.name} follows me.")
211223

212224
# Automatically accept the follow request
213-
id_ = "https://{HOST}/activity/{uuid.uuid4()}"
225+
id_ = f"https://{HOST}/activity/{uuid.uuid4()}"
214226
accept_activity = activity.accept(id_, actor)
215227

216228
# Send the signed Accept activity back to the follower's inbox

examples/send_message.py

Lines changed: 37 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@
55
import uuid
66
from datetime import UTC, datetime
77

8-
from apmodel.vocab.mention import Mention
8+
import apmodel
9+
from apmodel.core import Link
10+
from apmodel.objects import Actor, Mention
11+
from apmodel.webfinger import Resource as WebfingerResource
912
from cryptography.hazmat.primitives import serialization as crypto_serialization
10-
from cryptography.hazmat.primitives.asymmetric import rsa
13+
from cryptography.hazmat.primitives.asymmetric import ed25519, rsa
1114

12-
from apkit.client.asyncio import ActivityPubClient
13-
from apkit.client.models import Resource as WebfingerResource
15+
from apkit.client import ActivityPubClient
1416
from apkit.models import Create, CryptographicKey, Note, Person
17+
from apkit.types import ActorKey
1518

1619
if len(sys.argv) < 2:
1720
print("USAGE: python send_message.py <RECEPIENT_URI>", file=sys.stderr)
@@ -93,10 +96,16 @@ async def send_note(recepient: str) -> None:
9396
webfinger_result = await client.actor.resolve(res.username, res.host)
9497

9598
# read the ActivityPub link from the result
96-
recepient = webfinger_result.get("application/activity+json").href
99+
res = webfinger_result.get("application/activity+json")[0].href
100+
if not res:
101+
raise ValueError("")
102+
else:
103+
recepient = res
97104

98105
# Fetch a remote Actor
99106
target_actor = await client.actor.fetch(recepient)
107+
if not isinstance(target_actor, Actor):
108+
raise ValueError("Actor not found.")
100109
logger.info(f"Fetched actor: {target_actor.name}")
101110

102111
# Get the inbox URL from the actor's profile
@@ -106,6 +115,14 @@ async def send_note(recepient: str) -> None:
106115

107116
logger.info(f"Found actor's inbox: {inbox_url}")
108117

118+
if (
119+
actor.id is None
120+
or target_actor.id is None
121+
or target_actor.url is None
122+
or isinstance(target_actor.url, Link)
123+
):
124+
raise ValueError("Actor ID or URL is missing")
125+
109126
# Create note
110127
t = f'<p><span class="h-card" translate="no"><a href="{target_actor.url}" class="u-url mention">@<span>{target_actor.preferred_username}</span></a></span></p>'
111128
note = Note(
@@ -117,7 +134,8 @@ async def send_note(recepient: str) -> None:
117134
cc=["https://www.w3.org/ns/activitystreams#Public"],
118135
tag=[
119136
Mention(
120-
href=target_actor.url, name=f"@{target_actor.preferred_username}"
137+
href=target_actor.url,
138+
name=f"@{target_actor.preferred_username}",
121139
)
122140
],
123141
)
@@ -138,11 +156,21 @@ async def send_note(recepient: str) -> None:
138156
# Uncomment the following line if you want to see the code of the activity.
139157
# print(create.to_json())
140158

159+
if not actor.public_key:
160+
raise ValueError("Actor's publickey is missing")
161+
162+
if not isinstance(private_key, rsa.RSAPrivateKey) and not isinstance(
163+
private_key, ed25519.Ed25519PrivateKey
164+
):
165+
raise ValueError("Invalid Key")
166+
167+
if not actor.public_key.id:
168+
raise ValueError("public_key.key_id is required")
169+
141170
resp = await client.post(
142171
inbox_url,
143-
key_id=actor.publicKey.id,
144-
signature=private_key,
145-
json=create.to_json(keep_object=True),
172+
signatures=[ActorKey(key_id=actor.public_key.id, private_key=private_key)],
173+
json=apmodel.to_dict(create),
146174
)
147175
logger.info(f"Delivery result: {resp.status}")
148176

0 commit comments

Comments
 (0)