Source code for webmentions._model

from dataclasses import asdict, dataclass, field
from datetime import datetime, timezone
from enum import Enum


[docs] class WebmentionDirection(str, Enum): """ Enum representing the direction of a Webmention (incoming or outgoing). """ IN = "incoming" OUT = "outgoing"
[docs] @classmethod def from_raw(cls, raw: str) -> "WebmentionDirection": try: return cls(raw.strip().lower()) except ValueError: value = getattr(cls, raw.strip().upper(), None) if not value: raise ValueError(f"Unknown direction: {raw}") return value
[docs] class WebmentionStatus(str, Enum): """ Enum representing the status of a Webmention (pending, confirmed, or deleted). """ PENDING = "pending" CONFIRMED = "confirmed" DELETED = "deleted"
[docs] class ContentTextFormat(str, Enum): """ Supported content text formats. """ HTML = "html" MARKDOWN = "markdown" TEXT = "text"
[docs] class WebmentionType(str, Enum): """ Enum representing the type of Webmention. Note that this list is not exhaustive, and the Webmention recommendation itself does not provide any static list. This is however a lis of commonly supported types in Microformats. """ UNKNOWN = "unknown" MENTION = "mention" REPLY = "reply" LIKE = "like" REPOST = "repost" BOOKMARK = "bookmark" RSVP = "rsvp" FOLLOW = "follow"
[docs] @classmethod def from_raw(cls, raw: str | None) -> "WebmentionType": if not raw: return cls.UNKNOWN normalized = raw.strip().lower() aliases = { "in-reply-to": cls.REPLY, "reply": cls.REPLY, "like-of": cls.LIKE, "like": cls.LIKE, "repost-of": cls.REPOST, "repost": cls.REPOST, "bookmark-of": cls.BOOKMARK, "bookmark": cls.BOOKMARK, "rsvp": cls.RSVP, "follow-of": cls.FOLLOW, "follow": cls.FOLLOW, "mention": cls.MENTION, } return aliases.get(normalized, cls.UNKNOWN)
[docs] @dataclass class Webmention: """ Data class representing a Webmention. """ source: str target: str direction: WebmentionDirection title: str | None = None excerpt: str | None = None content: str | None = None author_name: str | None = None author_url: str | None = None author_photo: str | None = None published: datetime | None = None status: WebmentionStatus = WebmentionStatus.CONFIRMED mention_type: WebmentionType = WebmentionType.UNKNOWN mention_type_raw: str | None = None metadata: dict = field(default_factory=dict) created_at: datetime | None = None updated_at: datetime | None = None
[docs] def to_dict(self) -> dict: """ :return: A dictionary representation of the Webmention """ def _normalize(value): if value is None: return None if isinstance(value, datetime): return value.isoformat() if isinstance(value, Enum): return value.value if isinstance(value, list): return [_normalize(v) for v in value] if isinstance(value, tuple): return [_normalize(v) for v in value] if isinstance(value, dict): return {k: _normalize(v) for k, v in value.items()} return value return _normalize(asdict(self))
def __hash__(self): """ :return: A hash value based on the source, target, and direction. """ return hash((self.source, self.target, self.direction))
[docs] @classmethod def build( cls, data: dict, direction: WebmentionDirection = WebmentionDirection.IN ) -> "Webmention": assert data.get("source"), "source is required" assert data.get("target"), "target is required" mention_type: WebmentionType = ( data.get("mention_type") or WebmentionType.MENTION ) if isinstance(mention_type, str): mention_type = WebmentionType.from_raw(mention_type) def _parse_dt(value: object) -> datetime | None: dt = None if value is None: return None if isinstance(value, datetime): return value if isinstance(value, str) and value.strip(): dt = datetime.fromisoformat(value) if isinstance(value, (int, float)): dt = datetime.fromtimestamp(value, tz=timezone.utc) if dt and dt.tzinfo is None: dt = dt.replace(tzinfo=timezone.utc) return dt published = _parse_dt(data.get("published")) created_at = _parse_dt(data.get("created_at")) updated_at = _parse_dt(data.get("updated_at")) return cls( **{ **{k: v for k, v in data.items() if k in cls.__dataclass_fields__}, "direction": direction, "status": data.get("status") or WebmentionStatus.CONFIRMED, "mention_type": mention_type, "mention_type_raw": mention_type.value, "published": published, "created_at": created_at, "updated_at": updated_at, } )