diff --git a/great_ai/large_file/large_file/large_file_base.py b/great_ai/large_file/large_file/large_file_base.py index 1a30509..0d8ed0c 100644 --- a/great_ai/large_file/large_file/large_file_base.py +++ b/great_ai/large_file/large_file/large_file_base.py @@ -65,6 +65,8 @@ class LargeFileBase(ABC): keep_last_n: Optional[int] = None, cache_only_mode: bool = False, ): + if keep_last_n is not None and keep_last_n < 0: + raise ValueError("keep_last_n must be non-negative") clean_name = re.sub(r"[^!a-zA-Z0-9._-]+", "", name) if clean_name != name: raise ValueError( diff --git a/tests/large_file/test_storage_safety.py b/tests/large_file/test_storage_safety.py index 0df110e..c924bd3 100644 --- a/tests/large_file/test_storage_safety.py +++ b/tests/large_file/test_storage_safety.py @@ -130,6 +130,19 @@ def test_retention_counts_the_new_version() -> None: assert reader.get().read_text() == "2" +@pytest.mark.parametrize( + "implementation", [LargeFileLocal, LargeFileS3, LargeFileMongo] +) +def test_negative_retention_is_rejected_before_storage_access( + implementation: Any, +) -> None: + with LargeFileLocal("preserved", "w") as file: + file.write("weights") + with pytest.raises(ValueError, match="keep_last_n must be non-negative"): + implementation("preserved", "w", keep_last_n=-1) + assert LargeFileLocal("preserved").get().read_text() == "weights" + + def write_model(root: str, value: str, barrier: Any) -> None: LargeFileBase.cache_path = LargeFileLocal.cache_path = Path(root) LargeFileBase.max_cache_size = LargeFileLocal.max_cache_size = None