Coverage for src / kdbxtool / models / times.py: 97%
37 statements
« prev ^ index » next coverage.py v7.12.0, created at 2025-12-19 21:22 +0000
« prev ^ index » next coverage.py v7.12.0, created at 2025-12-19 21:22 +0000
1"""Timestamp handling for KDBX elements."""
3from __future__ import annotations
5from dataclasses import dataclass, field
6from datetime import UTC, datetime
9def _now() -> datetime:
10 """Get current UTC time."""
11 return datetime.now(UTC)
14@dataclass
15class Times:
16 """Timestamps associated with entries and groups.
18 All times are stored as timezone-aware UTC datetimes.
20 Attributes:
21 creation_time: When the element was created
22 last_modification_time: When the element was last modified
23 last_access_time: When the element was last accessed
24 expiry_time: When the element expires (if expires is True)
25 expires: Whether the element can expire
26 usage_count: Number of times the element has been used
27 location_changed: When the element was moved to a different group
28 """
30 creation_time: datetime = field(default_factory=_now)
31 last_modification_time: datetime = field(default_factory=_now)
32 last_access_time: datetime = field(default_factory=_now)
33 expiry_time: datetime | None = None
34 expires: bool = False
35 usage_count: int = 0
36 location_changed: datetime | None = None
38 def __post_init__(self) -> None:
39 """Ensure location_changed has a default value."""
40 if self.location_changed is None:
41 self.location_changed = self.creation_time
43 @property
44 def expired(self) -> bool:
45 """Check if the element has expired.
47 Returns:
48 True if expires is True and expiry_time is in the past
49 """
50 if not self.expires or self.expiry_time is None:
51 return False
52 return datetime.now(UTC) > self.expiry_time
54 def touch(self, modify: bool = False) -> None:
55 """Update access time, and optionally modification time.
57 Args:
58 modify: If True, also update modification time
59 """
60 now = _now()
61 self.last_access_time = now
62 if modify:
63 self.last_modification_time = now
65 def increment_usage(self) -> None:
66 """Increment usage count and update access time."""
67 self.usage_count += 1
68 self.touch()
70 def update_location(self) -> None:
71 """Update location_changed timestamp when element is moved."""
72 self.location_changed = _now()
73 self.touch(modify=True)
75 @classmethod
76 def create_new(
77 cls,
78 expires: bool = False,
79 expiry_time: datetime | None = None,
80 ) -> Times:
81 """Create timestamps for a new element.
83 Args:
84 expires: Whether the element can expire
85 expiry_time: When the element expires
87 Returns:
88 New Times instance with current timestamps
89 """
90 now = _now()
91 return cls(
92 creation_time=now,
93 last_modification_time=now,
94 last_access_time=now,
95 expiry_time=expiry_time,
96 expires=expires,
97 usage_count=0,
98 location_changed=now,
99 )