Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions docs/api/vocab.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ This section covers the specific models defined by the ActivityStreams 2.0 vocab
Actor types represent entities that can perform activities.

::: apmodel.vocab.actor.Actor
options:
show_root_heading: true
::: apmodel.vocab.actor.Person
options:
show_root_heading: true
Expand Down
30 changes: 28 additions & 2 deletions src/apmodel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,21 @@
from ._core._initial import _rebuild # noqa: F401
from ._version import __version__, __version_tuple__ # noqa: F401
from .context import LDContext
from .core.activity import Activity
from .core.collection import (
Collection,
CollectionPage,
OrderedCollection,
OrderedCollectionPage,
)
from .loader import load
from .vocab.activity.announce import Announce
from .vocab.activity.create import Create
from .vocab.activity.delete import Delete
from .vocab.activity.follow import Follow
from .vocab.activity.undo import Undo
from .vocab.actor import Person
from .vocab.note import Note


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


__all__ = [
"load",
"to_dict",
Announce,
Create,
Delete,
Undo,
Follow,
Activity,
Person,
Collection,
OrderedCollection,
CollectionPage,
OrderedCollectionPage,
Note,
load,
to_dict,
]
39 changes: 39 additions & 0 deletions src/apmodel/vocab/actor.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ class ActorEndpoints(Object):


class Actor(Object):
"""
Represents an ActivityStreams Actor.

Actors are entities that can perform activities.
"""

inbox: Optional[str | OrderedCollection] = Field(default=None)
outbox: Optional[str | OrderedCollection] = Field(default=None)
followers: Optional[str | OrderedCollection | Collection] = Field(default=None)
Expand All @@ -30,6 +36,39 @@ class Actor(Object):
public_key: Optional[CryptographicKey] = Field(default=None)
assertion_method: List[Multikey] = Field(default_factory=list)

@property
def keys(self) -> List[CryptographicKey | Multikey]:
"""
Provides a unified list of all keys associated with the actor.

This property combines `public_key` and `assertion_method` into a single
list for easier access.

Returns:
A list containing CryptographicKey and/or Multikey objects.
"""
ret: List[Multikey | CryptographicKey] = []
if self.public_key:
ret.append(self.public_key)
ret.extend(self.assertion_method)
return ret

def get_key(self, key_id: str) -> Optional[CryptographicKey | Multikey]:
"""
Finds a key by its ID from all keys associated with the actor.

Args:
key_id: The ID of the key to find.

Returns:
The key object (CryptographicKey or Multikey) if found,
otherwise None.
"""
for key in self.keys:
if key.id == key_id:
return key
return None

def _inference_context(self, result: dict) -> Dict[str, Any]:
result = super()._inference_context(result)

Expand Down
33 changes: 33 additions & 0 deletions tests/test_actor.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,36 @@ def test_actor_context_inference():
# The result should have the basic context
assert "@context" in inferred_result
assert "https://www.w3.org/ns/activitystreams" in inferred_result["@context"]


def test_actor_get_key():
from apmodel.extra.cid import Multikey
from apmodel.extra.security import CryptographicKey

pub_key = CryptographicKey(
id="https://example.com/actor/1#main-key",
owner="https://example.com/actor/1",
public_key_pem="-----BEGIN PUBLIC KEY-----\nMIIBI...IDAQAB\n-----END PUBLIC KEY-----\n",
)
multi_key = Multikey(
id="https://example.com/actor/1#multi-key",
controller="https://example.com/actor/1",
public_key_multibase="z6Mke...e6d3",
)

actor = Actor(
id="https://example.com/actor/1",
name="Test Actor",
public_key=pub_key,
assertion_method=[multi_key],
)

# Test keys property
assert len(actor.keys) == 2
assert pub_key in actor.keys
assert multi_key in actor.keys

# Test get_key method
assert actor.get_key("https://example.com/actor/1#main-key") == pub_key
assert actor.get_key("https://example.com/actor/1#multi-key") == multi_key
assert actor.get_key("non-existent-key") is None