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

1"""Timestamp handling for KDBX elements.""" 

2 

3from __future__ import annotations 

4 

5from dataclasses import dataclass, field 

6from datetime import UTC, datetime 

7 

8 

9def _now() -> datetime: 

10 """Get current UTC time.""" 

11 return datetime.now(UTC) 

12 

13 

14@dataclass 

15class Times: 

16 """Timestamps associated with entries and groups. 

17 

18 All times are stored as timezone-aware UTC datetimes. 

19 

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 """ 

29 

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 

37 

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 

42 

43 @property 

44 def expired(self) -> bool: 

45 """Check if the element has expired. 

46 

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 

53 

54 def touch(self, modify: bool = False) -> None: 

55 """Update access time, and optionally modification time. 

56 

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 

64 

65 def increment_usage(self) -> None: 

66 """Increment usage count and update access time.""" 

67 self.usage_count += 1 

68 self.touch() 

69 

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) 

74 

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. 

82 

83 Args: 

84 expires: Whether the element can expire 

85 expiry_time: When the element expires 

86 

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 )