Coverage for src/blob_dict/dict/git.py: 0%

83 statements  

« prev     ^ index     » next       coverage.py v7.8.1, created at 2025-05-21 20:53 -0700

1from collections.abc import Iterator 

2from datetime import UTC, datetime, timedelta 

3from typing import Any, override 

4 

5from extratools_git.repo import Repo 

6 

7from ..blob import BytesBlob 

8from .path import LocalPath, PathBlobDict 

9 

10 

11class GitBlobDict(PathBlobDict): 

12 def __init__( 

13 self, 

14 path: LocalPath, 

15 *, 

16 user_name: str, 

17 user_email: str, 

18 use_remote: bool = False, 

19 use_remote_frequence: timedelta = timedelta(minutes=1), 

20 **kwargs: Any, 

21 ) -> None: 

22 self.__repo_path: LocalPath = path.expanduser() 

23 

24 self.__repo: Repo = Repo.init( 

25 path, 

26 user_name=user_name, 

27 user_email=user_email, 

28 ) 

29 self.__user_name: str = user_name 

30 self.__user_email: str = user_email 

31 

32 self.__use_remote: bool = use_remote 

33 self.__use_remote_frequence: timedelta = use_remote_frequence 

34 self.__last_use_remote_time: datetime = datetime.now(UTC) - use_remote_frequence 

35 

36 super().__init__(self.__repo_path, **kwargs) 

37 

38 @override 

39 def create(self) -> None: 

40 super().create() 

41 

42 Repo.init( 

43 self.__repo_path, 

44 user_name=self.__user_name, 

45 user_email=self.__user_email, 

46 ) 

47 

48 @staticmethod 

49 def is_forbidden_key(key: str) -> bool: 

50 return key in {".git", ".gitignore"} or key.startswith(".git/") 

51 

52 __FORBIDDEN_KEY_ERROR_MESSAGE: str = "Cannot use any Git reserved file name as key" 

53 

54 @override 

55 def __contains__(self, key: str) -> bool: 

56 if self.is_forbidden_key(key): 

57 raise ValueError(self.__FORBIDDEN_KEY_ERROR_MESSAGE) 

58 

59 return super().__contains__(key) 

60 

61 def __can_use_remote(self) -> bool: 

62 return ( 

63 self.__use_remote 

64 and datetime.now(UTC) - self.__last_use_remote_time >= self.__use_remote_frequence 

65 ) 

66 

67 @override 

68 def __getitem__(self, key: str | tuple[str, Any], /) -> BytesBlob: 

69 if self.__can_use_remote(): 

70 self.__repo.pull(background=True) 

71 

72 if isinstance(key, str): 

73 return super().__getitem__(key) 

74 

75 try: 

76 key, version = key 

77 

78 return self._get( 

79 key, 

80 self.__repo.get_blob(key, version=version), 

81 ) 

82 except FileNotFoundError as e: 

83 raise KeyError from e 

84 

85 @override 

86 def __iter__(self) -> Iterator[str]: 

87 for child_path in self.__repo_path.iterdir(): 

88 if self.is_forbidden_key(child_path.name): 

89 continue 

90 

91 if child_path.is_dir(): 

92 for parent, _, files in child_path.walk(): 

93 for filename in files: 

94 yield str((parent / filename).relative_to(self.__repo_path)) 

95 else: 

96 yield str(child_path.relative_to(self.__repo_path)) 

97 

98 @override 

99 def pop(self, key: str, /, default: BytesBlob | None = None) -> BytesBlob | None: 

100 if self.is_forbidden_key(key): 

101 raise ValueError(self.__FORBIDDEN_KEY_ERROR_MESSAGE) 

102 

103 result: BytesBlob | None = super().pop(key, default) 

104 if result is None: 

105 return None 

106 

107 self.__repo.stage(key) 

108 self.__repo.commit(f"Delete {key}") 

109 

110 if self.__can_use_remote(): 

111 self.__repo.push(background=True) 

112 

113 return result 

114 

115 @override 

116 def __delitem__(self, key: str, /) -> None: 

117 if self.is_forbidden_key(key): 

118 raise ValueError(self.__FORBIDDEN_KEY_ERROR_MESSAGE) 

119 

120 super().__delitem__(key) 

121 

122 self.__repo.stage(key) 

123 self.__repo.commit(f"Delete {key}") 

124 

125 if self.__can_use_remote(): 

126 self.__repo.push(background=True) 

127 

128 @override 

129 def __setitem__(self, key: str, blob: BytesBlob, /) -> None: 

130 if self.is_forbidden_key(key): 

131 raise ValueError(self.__FORBIDDEN_KEY_ERROR_MESSAGE) 

132 

133 exists: bool = key in self 

134 

135 super().__setitem__(key, blob) 

136 

137 self.__repo.stage(key) 

138 self.__repo.commit(f"{"Update" if exists else "Add"} {key}") 

139 

140 if self.__can_use_remote(): 

141 self.__repo.push(background=True)