Coverage for src/lexigram/admin/gdpr/models.py: 100%

41 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-21 14:56 +0800

1"""GDPR data models — anonymization rules, SAR, and consent records.""" 

2 

3from __future__ import annotations 

4 

5from dataclasses import dataclass, field 

6from datetime import UTC, datetime 

7from enum import StrEnum 

8from typing import Any 

9 

10 

11class AnonymizationStrategy(StrEnum): 

12 """Strategy for anonymizing a single field. 

13 

14 * ``HASH`` — SHA-256 hex digest (irreversible, retains uniqueness) 

15 * ``CLEAR`` — Replace with empty string 

16 * ``REDACT`` — Replace with ``"[REDACTED]"`` 

17 * ``ZERO`` — Replace with ``0`` (for numeric fields) 

18 * ``NULLIFY`` — Replace with ``None`` 

19 * ``FAKE_EMAIL`` — Replace with ``"anonymized@example.com"`` 

20 """ 

21 

22 HASH = "hash" 

23 CLEAR = "clear" 

24 REDACT = "redact" 

25 ZERO = "zero" 

26 NULLIFY = "nullify" 

27 FAKE_EMAIL = "fake_email" 

28 

29 

30@dataclass 

31class AnonymizationRule: 

32 """Per-resource-type anonymization configuration. 

33 

34 Attributes: 

35 resource_type: Resource name this rule applies to (e.g. ``"user"``). 

36 fields: Mapping of field name → :class:`AnonymizationStrategy` (or 

37 string shorthand). 

38 preserve_id: When ``True``, the ``id`` field is always kept as-is. 

39 """ 

40 

41 resource_type: str 

42 fields: dict[str, str | AnonymizationStrategy] = field(default_factory=dict) 

43 preserve_id: bool = True 

44 

45 

46class SARStatus(StrEnum): 

47 """Subject Access Request lifecycle status.""" 

48 

49 PENDING = "pending" 

50 IN_PROGRESS = "in_progress" 

51 COMPLETED = "completed" 

52 REJECTED = "rejected" 

53 

54 

55@dataclass 

56class SubjectAccessRequest: 

57 """A GDPR Subject Access Request. 

58 

59 Attributes: 

60 sar_id: Unique identifier. 

61 subject_id: User/subject identifier who made the request. 

62 subject_email: Contact email for the response. 

63 status: Current lifecycle status. 

64 requested_at: UTC timestamp when the request was submitted. 

65 completed_at: UTC timestamp when the request was fulfilled. 

66 data_snapshot: Exported data (set when completed). 

67 notes: Internal notes. 

68 """ 

69 

70 sar_id: str 

71 subject_id: str 

72 subject_email: str 

73 status: SARStatus = SARStatus.PENDING 

74 requested_at: datetime = field(default_factory=lambda: datetime.now(UTC)) 

75 completed_at: datetime | None = None 

76 data_snapshot: dict[str, Any] = field(default_factory=dict) 

77 notes: str = "" 

78 

79 

80@dataclass 

81class ConsentRecord: 

82 """A single consent event for a subject. 

83 

84 Attributes: 

85 consent_id: Unique ID. 

86 subject_id: Who gave or withdrew consent. 

87 purpose: Consent purpose slug (e.g. ``"marketing_email"``). 

88 granted: ``True`` for grant, ``False`` for withdrawal. 

89 recorded_at: UTC timestamp of the event. 

90 metadata: Additional context (IP, user-agent, etc.). 

91 """ 

92 

93 consent_id: str 

94 subject_id: str 

95 purpose: str 

96 granted: bool 

97 recorded_at: datetime = field(default_factory=lambda: datetime.now(UTC)) 

98 metadata: dict[str, Any] = field(default_factory=dict) 

99 

100 

101__all__ = [ 

102 "AnonymizationRule", 

103 "AnonymizationStrategy", 

104 "ConsentRecord", 

105 "SARStatus", 

106 "SubjectAccessRequest", 

107]