Skip to content

Commit 954bbcd

Browse files
committed
wip
1 parent 41d9581 commit 954bbcd

7 files changed

Lines changed: 105 additions & 45 deletions

File tree

src/inline_snapshot/_change.py

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,29 @@
1616

1717
from inline_snapshot._external._external_location import Location
1818
from inline_snapshot._external._format import Format
19+
from inline_snapshot._external._format import get_format_handler_from_suffix
1920
from inline_snapshot._source_file import SourceFile
2021

2122
from ._rewrite_code import ChangeRecorder
2223
from ._rewrite_code import end_of
2324
from ._rewrite_code import start_of
2425

2526

27+
class ChangeBase:
28+
flag: str
29+
30+
def rich_diff(self):
31+
raise NotImplementedError
32+
33+
def apply_external_changes(self):
34+
raise NotImplementedError
35+
36+
def apply(self, recorder: ChangeRecorder):
37+
raise NotImplementedError
38+
39+
2640
@dataclass()
27-
class ExternalChange:
41+
class ExternalChange(ChangeBase):
2842
flag: str
2943

3044
new_file: Path
@@ -35,7 +49,6 @@ class ExternalChange:
3549
format: Format
3650

3751
def rich_diff(self):
38-
pass
3952

4053
title = str(self.new_location)
4154
if title != (old_name := str(self.old_location)):
@@ -58,10 +71,35 @@ def apply(self, recorder: ChangeRecorder):
5871

5972

6073
@dataclass()
61-
class Change:
74+
class ExternalRemove(ChangeBase):
75+
flag: str
76+
77+
old_location: Location
78+
79+
def rich_diff(self):
80+
81+
title = f"delete {self.old_location}"
82+
83+
with self.old_location.load() as old_file:
84+
assert self.old_location.suffix
85+
format = get_format_handler_from_suffix(self.old_location.suffix)
86+
return title, format.rich_show(old_file)
87+
88+
def apply_external_changes(self):
89+
self.old_location.delete()
90+
91+
def apply(self, recorder: ChangeRecorder):
92+
pass
93+
94+
95+
@dataclass()
96+
class Change(ChangeBase):
6297
flag: str
6398
file: SourceFile
6499

100+
def rich_diff(self):
101+
return None
102+
65103
@property
66104
def filename(self):
67105
return self.file.filename

src/inline_snapshot/_external/_external.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@ def __init__(self, name: str, expr, context: AdapterContext):
3232
The external data is by default stored inside `<pytest_config_dir>/.inline-snapshot/external`,
3333
where `<pytest_config_dir>` is replaced by the directory containing the Pytest configuration file, if any.
3434
To store data in a different location, set the `storage-dir` option in pyproject.toml.
35-
Data which is outsourced but not referenced in the source code jet has a '-new' suffix in the filename.
3635
3736
Parameters:
3837
name: the name of the external stored object.

src/inline_snapshot/_external/_external_location.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ def load(self) -> Generator[Path]:
2424
def store(self, new_file: Path):
2525
raise NotImplementedError
2626

27+
def delete(self):
28+
raise NotImplementedError
29+
2730
def exists(self):
2831
raise NotImplementedError
2932

@@ -57,7 +60,7 @@ def from_name(
5760
storage, path = name.split(":", 1)
5861
else:
5962
raise ValueError(
60-
"path has to be of the form <hash>.<suffix> or <partial_hash>*.<suffix>"
63+
f"path '{name}' has to be of the form <hash>.<suffix> or <partial_hash>*.<suffix>"
6164
)
6265

6366
if "." in path:
@@ -108,6 +111,14 @@ def store(self, new_file: Path):
108111
storage = state().all_storages[self.storage]
109112
storage.store(self, new_file)
110113

114+
def delete(self):
115+
from inline_snapshot._global_state import state
116+
117+
assert self.storage
118+
119+
storage = state().all_storages[self.storage]
120+
storage.delete(self)
121+
111122
def exists(self):
112123
return self.stem
113124

src/inline_snapshot/_external/_storage.py

Lines changed: 34 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66
import uuid
77
from contextlib import contextmanager
88
from pathlib import Path
9+
from typing import TYPE_CHECKING
910
from typing import Generator
11+
from typing import Iterator
12+
13+
if TYPE_CHECKING:
14+
from inline_snapshot._change import ChangeBase
1015

1116
from ._external_location import ExternalLocation
1217

@@ -35,6 +40,9 @@ def store(self, location: ExternalLocation, file_path: Path):
3540
"""
3641
raise NotImplementedError
3742

43+
def delete(self, location: ExternalLocation):
44+
raise NotImplementedError
45+
3846
def new_location(
3947
self, location: ExternalLocation, file_path: Path
4048
) -> ExternalLocation:
@@ -46,7 +54,9 @@ def new_location(
4654
def cleanup(self):
4755
raise NotImplementedError
4856

49-
def sync_used_externals(self, used_externals: list[ExternalLocation]) -> int:
57+
def sync_used_externals(
58+
self, used_externals: list[ExternalLocation]
59+
) -> Iterator[ChangeBase]:
5060
raise NotImplementedError
5161

5262

@@ -70,6 +80,10 @@ def store(self, location: ExternalLocation, file_path: Path):
7080

7181
shutil.copy(str(file_path), str(snapshot_path))
7282

83+
def delete(self, location: ExternalLocation):
84+
snapshot_path = self._get_path(location)
85+
snapshot_path.unlink()
86+
7387
def new_location(
7488
self, location: ExternalLocation, file_path: Path
7589
) -> ExternalLocation:
@@ -101,8 +115,10 @@ def _get_path(self, location: ExternalLocation) -> Path:
101115
def cleanup(self):
102116
pass
103117

104-
def sync_used_externals(self, used_externals: list[ExternalLocation]) -> int:
105-
return 0
118+
def sync_used_externals(
119+
self, used_externals: list[ExternalLocation]
120+
) -> Iterator[ChangeBase]:
121+
return iter(())
106122

107123

108124
class HashStorage(StorageProtocol):
@@ -129,13 +145,18 @@ def store(self, location: ExternalLocation, file_path: Path):
129145
with file_path.open("rb") as f:
130146
hash_name = file_digest(f, "sha256").hexdigest()
131147

148+
assert location.suffix
149+
132150
if not (self.directory / (hash_name + location.suffix)).exists():
133-
# TODO: remove -new
134151
shutil.copy(
135152
str(file_path),
136-
str(self.directory / (hash_name + "-new" + location.suffix)),
153+
str(self.directory / (hash_name + location.suffix)),
137154
)
138155

156+
def delete(self, location: ExternalLocation):
157+
path = self._lookup_path(location.path)
158+
path.unlink()
159+
139160
def new_location(
140161
self, location: ExternalLocation, file_path: Path
141162
) -> ExternalLocation:
@@ -154,24 +175,20 @@ def prune_new_files(self):
154175
for file in self.directory.glob("*-new.*"):
155176
file.unlink()
156177

157-
def sync_used_externals(self, used_externals: list[ExternalLocation]) -> int:
178+
def sync_used_externals(
179+
self, used_externals: list[ExternalLocation]
180+
) -> Iterator[ChangeBase]:
158181
unused_externals = self.list()
159182
for location in used_externals:
160183
used = self.lookup_all(location.path)
161-
for u in used:
162-
self.persist(u)
163184
unused_externals -= used
164185

165-
n = 0
166-
186+
from inline_snapshot._change import ExternalRemove
167187
from inline_snapshot._global_state import state
168188

169189
if state().update_flags.trim:
170190
for name in unused_externals:
171-
self.remove(name)
172-
n += 1
173-
174-
return n
191+
yield ExternalRemove("trim", ExternalLocation.from_name(name))
175192

176193
def cleanup(self):
177194
self.prune_new_files()
@@ -183,15 +200,6 @@ def list(self) -> set[str]:
183200
else:
184201
return set()
185202

186-
def persist(self, name):
187-
try:
188-
file = self._lookup_path(name)
189-
except StorageLookupError:
190-
return
191-
if file.stem.endswith("-new"):
192-
stem = file.stem[:-4]
193-
file.rename(file.with_name(stem + file.suffix))
194-
195203
def _lookup_path(self, name) -> Path:
196204
if "*" not in name:
197205
p = Path(name)
@@ -211,6 +219,9 @@ def _lookup_path(self, name) -> Path:
211219
def lookup_all(self, name: str) -> set[str]:
212220
return {file.name for file in self.directory.glob(name)}
213221

222+
def lookup_all_path(self, name: str) -> set[path]:
223+
return {file for file in self.directory.glob(name)}
224+
214225
def remove(self, name):
215226
self._lookup_path(name).unlink()
216227

src/inline_snapshot/pytest_plugin.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,14 @@ def apply_changes(flag):
384384
for category in all_categories:
385385
snapshot_changes[category] += 1
386386

387+
used_externals2 = _find_external.used_externals()
388+
389+
for name, storage in state().all_storages.items():
390+
for external_change in storage.sync_used_externals(
391+
[e for e in used_externals2 if e.storage == name]
392+
):
393+
changes[external_change.flag].append(external_change)
394+
387395
capture.suspend_global_capture(in_=True)
388396
try:
389397

@@ -482,8 +490,9 @@ def header():
482490
any_changes = True
483491

484492
for change in changes[flag]:
485-
if hasattr(change, "rich_diff"):
486-
title, content = change.rich_diff()
493+
diff = change.rich_diff()
494+
if diff is not None:
495+
title, content = diff
487496
console().print(
488497
Panel(
489498
content,
@@ -530,14 +539,6 @@ def header():
530539

531540
cr.fix_all()
532541

533-
used_externals2 = _find_external.used_externals()
534-
535-
for name, storage in state().all_storages.items():
536-
num = storage.sync_used_externals(
537-
[e for e in used_externals2 if e.storage == name]
538-
)
539-
if num:
540-
console().print(f"removed {num} unused externals\n")
541542
finally:
542543
capture.resume_global_capture()
543544

tests/test_external.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ def test_a():
4343
["--inline-snapshot=create"],
4444
changed_files=snapshot(
4545
{
46-
".inline-snapshot/external/ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb-new.txt": "a",
46+
".inline-snapshot/external/ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb.txt": "a",
4747
"test_something.py": """\
4848
from inline_snapshot import external
4949
def test_a():
@@ -105,7 +105,7 @@ def test_diskstorage():
105105

106106
with raises(
107107
snapshot(
108-
"StorageLookupError: hash collision files=['a140c0c1eda2def2b830363ba362aa4d7d255c262960544821f556e16661b6ff-new.txt', 'a4e624d686e03ed2767c0abd85c14426b0b1157d2ce81d27bb4fe4f6f01d688a-new.txt']"
108+
"StorageLookupError: hash collision files=['a140c0c1eda2def2b830363ba362aa4d7d255c262960544821f556e16661b6ff.txt', 'a4e624d686e03ed2767c0abd85c14426b0b1157d2ce81d27bb4fe4f6f01d688a.txt']"
109109
)
110110
):
111111
assert outsource("test4") == external("hash:a*.txt")
@@ -310,7 +310,7 @@ def test_a():
310310
project.run()
311311

312312
assert project.storage() == snapshot(
313-
["9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08-new.txt"]
313+
["9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08.txt"]
314314
)
315315

316316
project.run("--inline-snapshot=create")
@@ -368,7 +368,7 @@ def test_errors():
368368

369369
with raises(
370370
snapshot(
371-
"ValueError: path has to be of the form <hash>.<suffix> or <partial_hash>*.<suffix>"
371+
"ValueError: path 'invalid' has to be of the form <hash>.<suffix> or <partial_hash>*.<suffix>"
372372
)
373373
):
374374
external("invalid")
@@ -474,7 +474,7 @@ def test_something():
474474
assert project.storage() == snapshot(
475475
[
476476
"2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae.txt",
477-
"8dc140e6fe831481a2005ae152ffe32a9974aa92a260dfbac780d6a87154bb0b-new.txt",
477+
"8dc140e6fe831481a2005ae152ffe32a9974aa92a260dfbac780d6a87154bb0b.txt",
478478
]
479479
)
480480

@@ -499,7 +499,7 @@ def test_something():
499499
assert project.storage() == snapshot(
500500
[
501501
"2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae.txt",
502-
"8dc140e6fe831481a2005ae152ffe32a9974aa92a260dfbac780d6a87154bb0b-new.txt",
502+
"8dc140e6fe831481a2005ae152ffe32a9974aa92a260dfbac780d6a87154bb0b.txt",
503503
]
504504
)
505505

tests/test_external_formats.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ def test_a():
1515
["--inline-snapshot=create"],
1616
changed_files=snapshot(
1717
{
18-
".inline-snapshot/external/17f5ce5ea0f8711b6b20414da84373fb56176c3a3112c86c94529d3e29dacac3-new.json": """\
18+
".inline-snapshot/external/17f5ce5ea0f8711b6b20414da84373fb56176c3a3112c86c94529d3e29dacac3.json": """\
1919
[
2020
1,
2121
2

0 commit comments

Comments
 (0)