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

77 statements  

« prev     ^ index     » next       coverage.py v7.8.0, created at 2025-04-10 06:26 -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 get(self, key: str, default: BytesBlob | None = None) -> BytesBlob | None: 

69 if self.__can_use_remote(): 

70 self.__repo.pull(background=True) 

71 

72 return super().get(key, default) 

73 

74 @override 

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

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

77 if self.is_forbidden_key(child_path.name): 

78 continue 

79 

80 if child_path.is_dir(): 

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

82 for filename in files: 

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

84 else: 

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

86 

87 @override 

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

89 if self.is_forbidden_key(key): 

90 raise ValueError(self.__FORBIDDEN_KEY_ERROR_MESSAGE) 

91 

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

93 if result is None: 

94 return None 

95 

96 self.__repo.stage(key) 

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

98 

99 if self.__can_use_remote(): 

100 self.__repo.push(background=True) 

101 

102 return result 

103 

104 @override 

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

106 if self.is_forbidden_key(key): 

107 raise ValueError(self.__FORBIDDEN_KEY_ERROR_MESSAGE) 

108 

109 super().__delitem__(key) 

110 

111 self.__repo.stage(key) 

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

113 

114 if self.__can_use_remote(): 

115 self.__repo.push(background=True) 

116 

117 @override 

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

119 if self.is_forbidden_key(key): 

120 raise ValueError(self.__FORBIDDEN_KEY_ERROR_MESSAGE) 

121 

122 exists: bool = key in self 

123 

124 super().__setitem__(key, blob) 

125 

126 self.__repo.stage(key) 

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

128 

129 if self.__can_use_remote(): 

130 self.__repo.push(background=True)