Skip to content

Commit 29db112

Browse files
committed
some fixes
1 parent efc646a commit 29db112

File tree

7 files changed

+144
-32
lines changed

7 files changed

+144
-32
lines changed

README.md

Lines changed: 124 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,125 @@
11
# apmodel
2-
ActivityStreams 2.0 and nodeinfo 2.0/2.1 implementation for Python.
3-
## Other ActivityPub Tools
4-
- [apsig: Signature implementation used in ActivityPub](https://github.com/AmaseCocoa/apsig)
5-
- [apkit: Powerful Toolkit for ActivityPub Implementations](https://github.com/AmaseCocoa/apkit)
6-
## Links
7-
- [Official Fedi Account](https://hollo.amase.cc/@apkit)
2+
3+
[![PyPI version](https://badge.fury.io/py/apmodel.svg)](https://badge.fury.io/py/apmodel)
4+
[![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
5+
6+
Pydantic models for ActivityStreams 2.0, NodeInfo, and other Fediverse-related vocabularies.
7+
8+
`apmodel` provides model implementations for:
9+
- Activity Streams 2.0
10+
- [NodeInfo](https://nodeinfo.diasporafoundation.org/protocol.html) (2.0 and 2.1)
11+
- [CryptographicKey](https://w3c-ccg.github.io/security-vocab/contexts/security-v1.jsonld) (Security Vocabulary v1)
12+
- [Multikey](https://w3c-ccg.github.io/multikey/) and [DataIntegrityProof](https://w3c-ccg.github.io/data-integrity-spec/) (Controlled Identifiers v1.0)
13+
- [PropertyValue](https://schema.org/PropertyValue) (schema.org)
14+
15+
## Features
16+
17+
- **Type-Safe Models**: Leverages Pydantic for robust and type-safe data validation and manipulation.
18+
- **Automatic Model Loading**: A `load` function that automatically reads the `type` field from a dictionary and converts it to the correct Pydantic model.
19+
- **JSON-LD Aware**: Designed to work seamlessly with JSON-LD data, automatically handling expanded formats (e.g., values wrapped in arrays).
20+
- **Flexible Deserialization**: Handles unknown properties gracefully, allowing you to work with custom extensions in the ActivityPub ecosystem. Extra fields are attached directly to the model object.
21+
- **Comprehensive Vocabulary**: Includes a wide range of models from the ActivityStreams vocabulary, as well as common extensions used in the Fediverse.
22+
23+
## Installation
24+
25+
```bash
26+
pip install apmodel
27+
```
28+
29+
## Usage
30+
31+
### Loading Objects
32+
33+
Use the `apmodel.load()` function to parse a dictionary into a specific ActivityStreams model. The function inspects the `type` field to determine the correct model.
34+
35+
```python
36+
import apmodel
37+
38+
# Example ActivityStreams Note object as a dictionary
39+
note_data = {
40+
"@context": "https://www.w3.org/ns/activitystreams",
41+
"type": "Note",
42+
"content": "This is a simple note.",
43+
"published": "2025-05-10T12:00:00Z",
44+
"to": ["https://www.w3.org/ns/activitystreams#Public"],
45+
"cc": ["https://example.com/users/alice/followers"]
46+
}
47+
48+
# Load the dictionary into a Note object
49+
note_object = apmodel.load(note_data)
50+
51+
# note_object is now an instance of apmodel.vocab.Note
52+
print(type(note_object))
53+
# > <class 'apmodel.vocab.note.Note'>
54+
55+
# You can access attributes with type safety
56+
print(note_object.content)
57+
# > This is a simple note.
58+
59+
# The context is also parsed into an LDContext object
60+
print(note_object.context.to_json())
61+
# > ["https://www.w3.org/ns/activitystreams"]
62+
```
63+
64+
### Creating Objects
65+
66+
You can also create objects programmatically.
67+
68+
```python
69+
from apmodel.vocab import Create
70+
from apmodel.vocab import Note
71+
72+
note = Note(content="My new note!")
73+
create_activity = Create(
74+
actor="https://example.com/users/bob",
75+
object=note,
76+
)
77+
78+
# The model can be converted back to a dictionary
79+
# (Note: a dedicated `dump` function is not yet implemented,
80+
# but you can use Pydantic's `model_dump` for now)
81+
activity_dict = create_activity.model_dump(by_alias=True, exclude_none=True)
82+
83+
import json
84+
print(json.dumps(activity_dict, indent=2))
85+
```
86+
87+
This will output:
88+
```json
89+
{
90+
"@context": [
91+
"https://www.w3.org/ns/activitystreams"
92+
],
93+
"@type": "Create",
94+
"actor": "https://example.com/users/bob",
95+
"object": {
96+
"@context": [
97+
"https://www.w3.org/ns/activitystreams"
98+
],
99+
"@type": "Note",
100+
"content": "My new note!"
101+
}
102+
}
103+
```
104+
105+
## Development
106+
107+
This project uses [Task](https://taskfile.dev/) for running scripts.
108+
109+
### Setup Dev Environment
110+
111+
Installs development dependencies.
112+
113+
```bash
114+
task
115+
```
116+
117+
### Run Tests
118+
119+
```bash
120+
task test
121+
```
122+
123+
## License
124+
125+
This project is licensed under the MIT License.

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ version-file = "src/apmodel/_version.py"
4646
[dependency-groups]
4747
dev = [
4848
"git-cliff>=2.10.0",
49+
"mkdocs-gen-files>=0.5.0",
50+
"mkdocs-material>=9.6.23",
4951
"pytest>=8.4.1",
5052
]
5153

src/apmodel/__init__.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from ._initial import _rebuild # noqa: F401
12
from ._version import __version__, __version_tuple__ # noqa: F401
23

34
# from .dumper import dump
@@ -38,11 +39,3 @@
3839
# context
3940
"LDContext",
4041
]
41-
42-
Object.model_rebuild()
43-
Link.model_rebuild()
44-
Activity.model_rebuild()
45-
Collection.model_rebuild()
46-
OrderedCollection.model_rebuild()
47-
CollectionPage.model_rebuild()
48-
OrderedCollectionPage.model_rebuild()

src/apmodel/_version.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
commit_id: COMMIT_ID
2929
__commit_id__: COMMIT_ID
3030

31-
__version__ = version = '0.4.4.post1.dev2+g4ee26c515.d20251102'
32-
__version_tuple__ = version_tuple = (0, 4, 4, 'post1', 'dev2', 'g4ee26c515.d20251102')
31+
__version__ = version = '0.4.4.post1.dev4+g83d4bd29c.d20251104'
32+
__version_tuple__ = version_tuple = (0, 4, 4, 'post1', 'dev4', 'g83d4bd29c.d20251104')
3333

3434
__commit_id__ = commit_id = None

src/apmodel/core/object.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from typing import TYPE_CHECKING, Annotated, List, Optional, TypeVar, Union
44

5-
from pydantic import BeforeValidator, Field
5+
from pydantic import BeforeValidator, ConfigDict, Field
66

77
from apmodel.types.aliases import (
88
JSONLD_CONTEXT,
@@ -29,8 +29,13 @@
2929

3030

3131
class Object(ActivityPubModel):
32+
model_config = ConfigDict(
33+
serialize_by_alias=True
34+
)
35+
3236
context: JSONLD_CONTEXT = Field(
33-
alias="@context",
37+
validation_alias="@context",
38+
serialization_alias="@context",
3439
kw_only=True,
3540
default_factory=lambda: LDContext(
3641
["https://www.w3.org/ns/activitystreams"]
@@ -39,7 +44,7 @@ class Object(ActivityPubModel):
3944
id: OPT_STR = Field(
4045
validation_alias="@id", serialization_alias="@id", default=None
4146
)
42-
type: str = Field(
47+
type: Annotated[str, BeforeValidator(get_value_from_array)] = Field(
4348
alias="@type",
4449
default="https://www.w3.org/ns/activitystreams#Object",
4550
kw_only=True,

src/apmodel/extra/emoji.py

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
1-
from typing import Annotated, Optional
1+
from __future__ import annotations
22

3-
from pydantic import BeforeValidator, Field
4-
5-
from apmodel.types.aliases import OPT_STR
3+
from pydantic import Field
64

75
from ..core import Object
8-
from ..helpers import get_value_from_array
96

107

118
class Emoji(Object):
12-
type: str = (
13-
Field(
14-
alias="@type",
15-
default="http://joinmastodon.org/ns#Emoji",
16-
kw_only=True,
17-
)
9+
type: str = Field(
10+
alias="@type",
11+
default="http://joinmastodon.org/ns#Emoji",
12+
kw_only=True,
1813
)

src/apmodel/types/aliases.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
from datetime import datetime
2-
from typing import TYPE_CHECKING, Annotated, Optional, Union
2+
from typing import TYPE_CHECKING, Annotated, Optional, TypeAlias, Union
33

44
from cryptography.hazmat.primitives.asymmetric import ed25519, rsa
55
from pydantic import AfterValidator, BeforeValidator
6-
from typing_extensions import TypeAlias
76

87
from ..context import LDContext
98
from ..helpers import (

0 commit comments

Comments
 (0)