tgzr.snap 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,287 @@
1
+ from __future__ import annotations
2
+
3
+ from pathlib import Path
4
+ import json
5
+ import hashlib
6
+ import shutil
7
+ import os
8
+ import stat
9
+ import dataclasses
10
+ import tarfile
11
+ import tempfile
12
+
13
+ from ..base_repository import SnapRepository, RepositoryConfig, Remote, VersionInfo
14
+
15
+
16
+ class FolderRepositoryConfig(RepositoryConfig):
17
+ pass
18
+
19
+
20
+ class FolderSnap(SnapRepository[FolderRepositoryConfig]):
21
+
22
+ @classmethod
23
+ def create_repository(
24
+ cls, path: str | Path, config: RepositoryConfig, force: bool = False
25
+ ):
26
+ path = Path(path)
27
+ path.mkdir(parents=True, exist_ok=force)
28
+ dot_snap = path / ".snap"
29
+ with dot_snap.open("w") as fp:
30
+ json.dump(dataclasses.asdict(config), fp)
31
+
32
+ for remote in config.remotes:
33
+ if not Path(remote.url).is_dir():
34
+ raise ValueError(
35
+ f"The remote {remote.name} url is invalid (not a directory): {remote.url}"
36
+ )
37
+
38
+ def _make_tarfile(self, dir_path: str | Path) -> str:
39
+ dir_path = Path(dir_path)
40
+ temp_fd, temp_path = tempfile.mkstemp(suffix=".tgz")
41
+
42
+ def filter(o: tarfile.TarInfo):
43
+ print(f"Adding {o.name} to archive...")
44
+ return o
45
+
46
+ with tarfile.open(temp_path, "w") as tar:
47
+ tar.add(dir_path, arcname=dir_path.name, filter=filter)
48
+ return temp_path
49
+
50
+ def _untar(self, tar_path: str | Path, destination: str | Path):
51
+ with tarfile.open(tar_path) as f:
52
+ f.extractall(path=destination)
53
+
54
+ def _get_hash(self, path: str | Path):
55
+ hasher = hashlib.md5()
56
+ with open(path, "rb") as f:
57
+ for chunk in iter(lambda: f.read(4096), b""):
58
+ hasher.update(chunk)
59
+ return hasher.hexdigest()
60
+
61
+ #
62
+ #
63
+ #
64
+
65
+ def default_remote_name(self) -> str:
66
+ return self.config.default_remote
67
+
68
+ def remotes(self) -> list[Remote]:
69
+ return self.config.remotes.copy()
70
+
71
+ def get_remote(self, remote_name: str | None) -> Remote:
72
+ remote_name = remote_name or self.config.default_remote
73
+ for remote in self.config.remotes:
74
+ if remote.name == remote_name:
75
+ return remote
76
+ raise KeyError(
77
+ f"Unknown remote {remote_name!r} (got {[r.name for r in self.config.remotes]})."
78
+ )
79
+
80
+ #
81
+ #
82
+ #
83
+
84
+ def push(self, repo_path: str, remote_name: str | None = None) -> VersionInfo:
85
+ path = self.path / repo_path
86
+ is_dir = path.is_dir()
87
+ data_name = "data"
88
+ if path.is_dir():
89
+ tar_path = self._make_tarfile(path)
90
+ path = Path(tar_path)
91
+
92
+ file_hash = self._get_hash(path)
93
+ if is_dir:
94
+ file_hash += ".dir"
95
+ data_name = "data.tar"
96
+ remote = self.get_remote(remote_name)
97
+ remote_dir = Path(remote.url) / file_hash
98
+ remote_dir.mkdir(exist_ok=True)
99
+
100
+ shutil.copy2(path, remote_dir / data_name)
101
+
102
+ pstat = path.stat()
103
+ vi = VersionInfo(
104
+ name=file_hash,
105
+ isdir=is_dir,
106
+ size=pstat.st_size,
107
+ nfiles=None,
108
+ isexec=bool(pstat.st_mode & stat.S_IXUSR),
109
+ version_id=None,
110
+ md5=None,
111
+ mtime=pstat.st_mtime,
112
+ remote_name=remote.name,
113
+ )
114
+ return vi
115
+
116
+ def get_versions(self, repo_path: str):
117
+ path = self.path / repo_path
118
+ raise NotImplementedError()
119
+
120
+ def status(self, repo_path: str):
121
+ path = self.path / repo_path
122
+ raise NotImplementedError()
123
+
124
+ def pull(self, repo_path: str | Path, version: str, remote_name: str | None = None):
125
+ path = self.path / repo_path
126
+ data_name = "data"
127
+ remote = self.get_remote(remote_name)
128
+
129
+ is_dir = version.endswith(".dir")
130
+ if is_dir:
131
+ data_name += ".tar"
132
+ remote_path = Path(remote.url) / version / data_name
133
+ if not remote_path.exists():
134
+ raise Exception(f"Version {version} not found ({remote_path})")
135
+
136
+ if is_dir:
137
+ self._untar(remote_path, path)
138
+ else:
139
+ shutil.copy2(remote_path, path)
140
+
141
+ def unprotect(self, local_path: str):
142
+ # Simply ensure the user has write permissions
143
+ current_mode = os.stat(local_path).st_mode
144
+ os.chmod(local_path, current_mode | stat.S_IWRITE)
145
+
146
+
147
+ #
148
+ #
149
+ #
150
+
151
+
152
+ def test_config() -> FolderRepositoryConfig:
153
+ config = FolderRepositoryConfig(
154
+ remotes=[
155
+ Remote("blessed", "/tmp/snap_tests_folder/PROD"),
156
+ Remote("review", "/tmp/snap_tests_folder/REVIEW"),
157
+ ],
158
+ default_remote="review",
159
+ )
160
+ return config
161
+
162
+
163
+ def create_repo():
164
+ config = test_config()
165
+ path = Path("/tmp/snap_tests_folder/WORK")
166
+ force = True
167
+ for remote in config.remotes:
168
+ Path(remote.url).mkdir(exist_ok=True, parents=False)
169
+ FolderSnap.create_repository(path, config, force=force)
170
+
171
+
172
+ def test_push():
173
+ import time
174
+
175
+ config = test_config()
176
+ path = Path("/tmp/snap_tests_folder/WORK")
177
+ snap = FolderSnap(path, config)
178
+ artifact = "bob.txt"
179
+
180
+ snap.unprotect(artifact)
181
+
182
+ with open(snap.path / artifact, "w") as fp:
183
+ fp.write(f"Change... {time.time()}")
184
+
185
+ version = snap.push(artifact)
186
+
187
+ print(f" version='{version.name}'")
188
+
189
+
190
+ def test_pull():
191
+
192
+ config = test_config()
193
+ path = Path("/tmp/snap_tests_folder/WORK")
194
+ snap = FolderSnap(path, config)
195
+ artifact = "bob.txt"
196
+
197
+ version = "bba2820fbf33bc4aef1444eca8c745da"
198
+ version = "cc2fcf6e6f788f6a9c66955d8407b196"
199
+
200
+ snap.pull(artifact, version)
201
+ with open(path / artifact, "r") as fp:
202
+ print("CONTENT is:", fp.read())
203
+
204
+
205
+ def test_push_iso():
206
+ import rich
207
+
208
+ config = test_config()
209
+ path = Path("/tmp/snap_tests_folder/WORK")
210
+ snap = FolderSnap(path, config)
211
+ artifact = "big.iso"
212
+
213
+ Mo = 360000
214
+ with open(path / artifact, "w") as fp:
215
+ fp.write(str(os.urandom(500 * Mo)))
216
+
217
+ version_info = snap.push(artifact)
218
+
219
+ rich.print(f"{version_info}")
220
+ print(f" version='{version_info.name}'")
221
+
222
+
223
+ def test_pull_iso():
224
+ import time
225
+
226
+ config = test_config()
227
+ path = Path("/tmp/snap_tests_folder/WORK")
228
+ snap = FolderSnap(path, config)
229
+ artifact = "big.iso"
230
+
231
+ version = "ecdf67fa6c06ae448ad0d5b6a76350e4" # 9.9M
232
+ # version = "051223e2f81bb5242b9423ad4e6331a0" # 99M
233
+ # version = "e973302bab90201b4c865a16dbfb35b8" # 493M
234
+
235
+ t = time.time()
236
+ snap.pull(artifact, version)
237
+ print(time.time() - t)
238
+
239
+
240
+ def test_push_dir():
241
+ import random, rich
242
+
243
+ config = test_config()
244
+ path = Path("/tmp/snap_tests_folder/WORK")
245
+ snap = FolderSnap(path, config)
246
+ artifact = "data_folder"
247
+
248
+ (path / artifact).mkdir(exist_ok=True)
249
+
250
+ for i in range(random.randrange(5, 10)):
251
+ Mo = 360000
252
+ size = random.randrange(1, 20)
253
+ with open(path / artifact / f"file_{i:03}.data", "w") as fp:
254
+ fp.write(str(os.urandom(size * Mo)))
255
+
256
+ version_info = snap.push(artifact)
257
+
258
+ rich.print(f"{version_info}")
259
+ print(f" version='{version_info.name}'")
260
+
261
+
262
+ def test_pull_dir():
263
+
264
+ import time
265
+
266
+ config = test_config()
267
+ path = Path("/tmp/snap_tests_folder/WORK")
268
+ snap = FolderSnap(path, config)
269
+ artifact = "data_folder"
270
+
271
+ version = "476d2b77e9f9356f8d47c8711c41ebe8.dir"
272
+ version = "ff844fed890f1623075ddfcc7e31e4ad.dir"
273
+ # version = "1ead82a778cfae4b1f1a517f9e04589b.dir"
274
+
275
+ t = time.time()
276
+ snap.pull(artifact, version)
277
+ print(time.time() - t)
278
+
279
+
280
+ if __name__ == "__main__":
281
+ # create_repo()
282
+ # test_push()
283
+ # test_pull()
284
+ # test_push_iso()
285
+ # test_pull_iso()
286
+ # test_push_dir()
287
+ test_pull_dir()
@@ -0,0 +1,26 @@
1
+ Metadata-Version: 2.4
2
+ Name: tgzr.snap
3
+ Version: 0.0.1
4
+ Summary: Snap Normalizes Artifact Persistence - tgzr artifacts repository API
5
+ Project-URL: Documentation, https://github.com/open-tgzr/tgzr.snap#readme
6
+ Project-URL: Issues, https://github.com/open-tgzr/tgzr.snap/issues
7
+ Project-URL: Source, https://github.com/open-tgzr/tgzr.snap
8
+ Author-email: Dee <dee.sometech@gmail.com>
9
+ License-Expression: GPL-3.0-or-later
10
+ License-File: LICENSE
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Programming Language :: Python
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: Implementation :: CPython
19
+ Classifier: Programming Language :: Python :: Implementation :: PyPy
20
+ Requires-Python: >=3.9
21
+ Requires-Dist: dvc>=3.66.1
22
+ Requires-Dist: tgzr-package-management>=0.101
23
+ Description-Content-Type: text/markdown
24
+
25
+ # tgzr.snap
26
+ Snap Normalizes Artifact Persistence - tgzr artifacts repository API.
@@ -0,0 +1,12 @@
1
+ tgzr/snap/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
2
+ tgzr/snap/_version.py,sha256=qf6R-J7-UyuABBo8c0HgaquJ8bejVbf07HodXgwAwgQ,704
3
+ tgzr/snap/base_repository.py,sha256=cWQAASq0HTeXYuDfkp871X9FKWYqdEfyR409m8UB9Qc,2712
4
+ tgzr/snap/plugin.py,sha256=VbVLM5qgy0XAXW2VVRFjFqn0YDO7zptwzYsj5zs_KBc,963
5
+ tgzr/snap/plugins/__init__.py,sha256=rszdLaaCDzBat7AtPiOPNksxJ6WPRX33QTWXi3mtdew,407
6
+ tgzr/snap/plugins/dvc.py,sha256=jBBQxXOEi67HsmEWgh6G-pIQZTSWHtlDUFQOduGSWAk,10404
7
+ tgzr/snap/plugins/folder.py,sha256=tcf0Nku1SB2_t-yHDXtFq8-s21YLrl2mDSMS_ZoEaFU,7642
8
+ tgzr_snap-0.0.1.dist-info/METADATA,sha256=Ng0Pk2YBPiqJL5ZJ6fmZoFaBLBFiWKVrdBfNTpHb_Rk,1133
9
+ tgzr_snap-0.0.1.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
10
+ tgzr_snap-0.0.1.dist-info/entry_points.txt,sha256=KSrm3fgVbJ2kxCsTg6kxppSo3kOWcbS6iQXehpjK9VM,101
11
+ tgzr_snap-0.0.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
12
+ tgzr_snap-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.28.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,3 @@
1
+ [tgzr.snap.plugin]
2
+ dvc = tgzr.snap.plugins:DVCSnapPlugin
3
+ folder = tgzr.snap.plugins:FolderSnapPlugin