Coverage for src/blob_dict/dict/git.py: 0%
77 statements
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-09 03:05 -0700
« prev ^ index » next coverage.py v7.8.0, created at 2025-04-09 03:05 -0700
1from collections.abc import Iterator
2from datetime import UTC, datetime, timedelta
3from typing import Any, override
5from extratools_git.repo import Repo
7from ..blob import BytesBlob
8from .path import LocalPath, PathBlobDict
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()
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
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
36 super().__init__(self.__repo_path, **kwargs)
38 @override
39 def create(self) -> None:
40 super().create()
42 Repo.init(
43 self.__repo_path,
44 user_name=self.__user_name,
45 user_email=self.__user_email,
46 )
48 @staticmethod
49 def is_forbidden_key(key: str) -> bool:
50 return key in {".git", ".gitignore"} or key.startswith(".git/")
52 __FORBIDDEN_KEY_ERROR_MESSAGE: str = "Cannot use any Git reserved file name as key"
54 @override
55 def __contains__(self, key: str) -> bool:
56 if self.is_forbidden_key(key):
57 raise ValueError(self.__FORBIDDEN_KEY_ERROR_MESSAGE)
59 return super().__contains__(key)
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 )
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)
72 return super().get(key, default)
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
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))
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)
92 result: BytesBlob | None = super().pop(key, default)
93 if result is None:
94 return None
96 self.__repo.stage(key)
97 self.__repo.commit(f"Delete {key}")
99 if self.__can_use_remote():
100 self.__repo.push(background=True)
102 return result
104 @override
105 def __delitem__(self, key: str) -> None:
106 if self.is_forbidden_key(key):
107 raise ValueError(self.__FORBIDDEN_KEY_ERROR_MESSAGE)
109 super().__delitem__(key)
111 self.__repo.stage(key)
112 self.__repo.commit(f"Delete {key}")
114 if self.__can_use_remote():
115 self.__repo.push(background=True)
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)
122 exists: bool = key in self
124 super().__setitem__(key, blob)
126 self.__repo.stage(key)
127 self.__repo.commit(f"{"Update" if exists else "Add"} {key}")
129 if self.__can_use_remote():
130 self.__repo.push(background=True)