Skip to content

Commit 9a9c00f

Browse files
authored
Merge pull request #16 from fedi-libs/feat/key-in-one
feat(actor): Add key retrieval functionality to Actor class
2 parents 371e1ee + f9c644e commit 9a9c00f

File tree

4 files changed

+106
-4
lines changed

4 files changed

+106
-4
lines changed

docs/api/vocab.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@ This section covers the specific models defined by the ActivityStreams 2.0 vocab
77
Actor types represent entities that can perform activities.
88

99
::: apmodel.vocab.actor.Actor
10-
options:
11-
show_root_heading: true
1210
::: apmodel.vocab.actor.Person
1311
options:
1412
show_root_heading: true

src/apmodel/__init__.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,21 @@
55
from ._core._initial import _rebuild # noqa: F401
66
from ._version import __version__, __version_tuple__ # noqa: F401
77
from .context import LDContext
8+
from .core.activity import Activity
9+
from .core.collection import (
10+
Collection,
11+
CollectionPage,
12+
OrderedCollection,
13+
OrderedCollectionPage,
14+
)
815
from .loader import load
16+
from .vocab.activity.announce import Announce
17+
from .vocab.activity.create import Create
18+
from .vocab.activity.delete import Delete
19+
from .vocab.activity.follow import Follow
20+
from .vocab.activity.undo import Undo
21+
from .vocab.actor import Person
22+
from .vocab.note import Note
923

1024

1125
def to_dict(obj: ActivityPubModel, **options) -> dict:
@@ -43,6 +57,18 @@ def extract_and_clean(data: Any) -> Any:
4357

4458

4559
__all__ = [
60+
"Activity",
61+
"Announce",
62+
"Collection",
63+
"CollectionPage",
64+
"Create",
65+
"Delete",
66+
"Follow",
67+
"Note",
68+
"OrderedCollection",
69+
"OrderedCollectionPage",
70+
"Person",
71+
"Undo",
4672
"load",
4773
"to_dict",
4874
]

src/apmodel/vocab/actor.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ class ActorEndpoints(Object):
1515

1616

1717
class Actor(Object):
18+
"""
19+
Represents an ActivityStreams Actor.
20+
21+
Actors are entities that can perform activities.
22+
"""
23+
1824
inbox: Optional[str | OrderedCollection] = Field(default=None)
1925
outbox: Optional[str | OrderedCollection] = Field(default=None)
2026
followers: Optional[str | OrderedCollection | Collection] = Field(default=None)
@@ -30,6 +36,36 @@ class Actor(Object):
3036
public_key: Optional[CryptographicKey] = Field(default=None)
3137
assertion_method: List[Multikey] = Field(default_factory=list)
3238

39+
@property
40+
def keys(self) -> List[CryptographicKey | Multikey]:
41+
"""
42+
Provides a unified list of all keys associated with the actor.
43+
44+
This property combines `public_key` and `assertion_method` into a single
45+
list for easier access.
46+
47+
Returns:
48+
A list containing CryptographicKey and/or Multikey objects.
49+
"""
50+
ret: List[Multikey | CryptographicKey] = []
51+
if self.public_key:
52+
ret.append(self.public_key)
53+
ret.extend(self.assertion_method)
54+
return ret
55+
56+
def get_key(self, key_id: str) -> Optional[CryptographicKey | Multikey]:
57+
"""
58+
Finds a key by its ID from all keys associated with the actor.
59+
60+
Args:
61+
key_id: The ID of the key to find.
62+
63+
Returns:
64+
The key object (CryptographicKey or Multikey) if found,
65+
otherwise None.
66+
"""
67+
return next((key for key in self.keys if key.id == key_id), None)
68+
3369
def _inference_context(self, result: dict) -> Dict[str, Any]:
3470
result = super()._inference_context(result)
3571

tests/test_actor.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1-
from apmodel.vocab.actor import Actor, Person, Application, Group, Organization, Service
21
from apmodel.core.collection import OrderedCollection
2+
from apmodel.vocab.actor import (
3+
Actor,
4+
Application,
5+
Group,
6+
Organization,
7+
Person,
8+
Service,
9+
)
310

411

512
def test_actor_creation():
@@ -46,7 +53,9 @@ def test_actor_with_ordered_collections():
4653
# Test creating an Actor with OrderedCollection fields
4754
inbox_collection = OrderedCollection(id="https://example.com/actor/1/inbox")
4855
actor = Actor(
49-
id="https://example.com/actor/1", name="Test Actor", inbox=inbox_collection
56+
id="https://example.com/actor/1",
57+
name="Test Actor",
58+
inbox=inbox_collection,
5059
)
5160

5261
assert actor.id == "https://example.com/actor/1"
@@ -104,3 +113,36 @@ def test_actor_context_inference():
104113
# The result should have the basic context
105114
assert "@context" in inferred_result
106115
assert "https://www.w3.org/ns/activitystreams" in inferred_result["@context"]
116+
117+
118+
def test_actor_get_key():
119+
from apmodel.extra.cid import Multikey
120+
from apmodel.extra.security import CryptographicKey
121+
122+
pub_key = CryptographicKey(
123+
id="https://example.com/actor/1#main-key",
124+
owner="https://example.com/actor/1",
125+
public_key_pem="-----BEGIN PUBLIC KEY-----\nMIIBI...IDAQAB\n-----END PUBLIC KEY-----\n",
126+
)
127+
multi_key = Multikey(
128+
id="https://example.com/actor/1#multi-key",
129+
controller="https://example.com/actor/1",
130+
public_key_multibase="z6Mke...e6d3",
131+
)
132+
133+
actor = Actor(
134+
id="https://example.com/actor/1",
135+
name="Test Actor",
136+
public_key=pub_key,
137+
assertion_method=[multi_key],
138+
)
139+
140+
# Test keys property
141+
assert len(actor.keys) == 2
142+
assert pub_key in actor.keys
143+
assert multi_key in actor.keys
144+
145+
# Test get_key method
146+
assert actor.get_key("https://example.com/actor/1#main-key") == pub_key
147+
assert actor.get_key("https://example.com/actor/1#multi-key") == multi_key
148+
assert actor.get_key("non-existent-key") is None

0 commit comments

Comments
 (0)