feat: add event tables schema and models
Create migration for events, event_revisions, event_tombstones, idempotency_nonces, and event_animals tables with ULID checks and JSON validation. Add Pydantic models with field validators. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,13 @@
|
||||
# ABOUTME: Models package for AnimalTrack.
|
||||
# ABOUTME: Exports all Pydantic models and enums for reference data.
|
||||
# ABOUTME: Exports all Pydantic models and enums for reference and event data.
|
||||
|
||||
from animaltrack.models.events import (
|
||||
Event,
|
||||
EventAnimal,
|
||||
EventRevision,
|
||||
EventTombstone,
|
||||
IdempotencyNonce,
|
||||
)
|
||||
from animaltrack.models.reference import (
|
||||
FeedType,
|
||||
Location,
|
||||
@@ -12,7 +19,12 @@ from animaltrack.models.reference import (
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"Event",
|
||||
"EventAnimal",
|
||||
"EventRevision",
|
||||
"EventTombstone",
|
||||
"FeedType",
|
||||
"IdempotencyNonce",
|
||||
"Location",
|
||||
"Product",
|
||||
"ProductUnit",
|
||||
|
||||
92
src/animaltrack/models/events.py
Normal file
92
src/animaltrack/models/events.py
Normal file
@@ -0,0 +1,92 @@
|
||||
# ABOUTME: Pydantic models for event sourcing tables.
|
||||
# ABOUTME: Includes Event, EventRevision, EventTombstone, IdempotencyNonce, EventAnimal.
|
||||
|
||||
from pydantic import BaseModel, Field, field_validator
|
||||
|
||||
|
||||
class Event(BaseModel):
|
||||
"""Core event model representing a state change in the system."""
|
||||
|
||||
id: str = Field(..., min_length=26, max_length=26)
|
||||
type: str
|
||||
ts_utc: int
|
||||
actor: str
|
||||
entity_refs: dict
|
||||
payload: dict
|
||||
version: int = 1
|
||||
|
||||
@field_validator("id")
|
||||
@classmethod
|
||||
def id_must_be_26_chars(cls, v: str) -> str:
|
||||
"""Event ID must be exactly 26 characters (ULID format)."""
|
||||
if len(v) != 26:
|
||||
msg = "Event ID must be exactly 26 characters"
|
||||
raise ValueError(msg)
|
||||
return v
|
||||
|
||||
|
||||
class EventRevision(BaseModel):
|
||||
"""Stores prior versions of events when they are edited."""
|
||||
|
||||
event_id: str = Field(..., min_length=26, max_length=26)
|
||||
version: int
|
||||
ts_utc: int
|
||||
actor: str
|
||||
entity_refs: dict
|
||||
payload: dict
|
||||
edited_at_utc: int
|
||||
edited_by: str
|
||||
|
||||
@field_validator("event_id")
|
||||
@classmethod
|
||||
def event_id_must_be_26_chars(cls, v: str) -> str:
|
||||
"""Event ID must be exactly 26 characters (ULID format)."""
|
||||
if len(v) != 26:
|
||||
msg = "Event ID must be exactly 26 characters"
|
||||
raise ValueError(msg)
|
||||
return v
|
||||
|
||||
|
||||
class EventTombstone(BaseModel):
|
||||
"""Immutable record of event deletion."""
|
||||
|
||||
id: str = Field(..., min_length=26, max_length=26)
|
||||
ts_utc: int
|
||||
actor: str
|
||||
target_event_id: str = Field(..., min_length=26, max_length=26)
|
||||
reason: str | None = None
|
||||
|
||||
@field_validator("id", "target_event_id")
|
||||
@classmethod
|
||||
def ulid_must_be_26_chars(cls, v: str) -> str:
|
||||
"""ULID fields must be exactly 26 characters."""
|
||||
if len(v) != 26:
|
||||
msg = "ULID must be exactly 26 characters"
|
||||
raise ValueError(msg)
|
||||
return v
|
||||
|
||||
|
||||
class IdempotencyNonce(BaseModel):
|
||||
"""Tracks POST form nonces to prevent duplicate submissions."""
|
||||
|
||||
nonce: str
|
||||
actor: str
|
||||
route: str
|
||||
created_at_utc: int
|
||||
|
||||
|
||||
class EventAnimal(BaseModel):
|
||||
"""Links events to affected animals for efficient querying."""
|
||||
|
||||
event_id: str = Field(..., min_length=26, max_length=26)
|
||||
animal_id: str = Field(..., min_length=26, max_length=26)
|
||||
ts_utc: int
|
||||
|
||||
@field_validator("event_id", "animal_id")
|
||||
@classmethod
|
||||
def ulid_must_be_26_chars(cls, v: str) -> str:
|
||||
"""ULID fields must be exactly 26 characters."""
|
||||
if len(v) != 26:
|
||||
msg = "ULID must be exactly 26 characters"
|
||||
raise ValueError(msg)
|
||||
return v
|
||||
Reference in New Issue
Block a user