From 5d12d44a145cf6e6ad4ba0fbe8b47bdb17d9dc18 Mon Sep 17 00:00:00 2001 From: KurosawaAngel <145038102+KurosawaAngel@users.noreply.github.com> Date: Tue, 21 Jan 2025 17:08:25 +0500 Subject: [PATCH 1/7] add check mtime for path --- src/aiogram_dialog/context/media_storage.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/aiogram_dialog/context/media_storage.py b/src/aiogram_dialog/context/media_storage.py index 6ba290da..0263ffd1 100644 --- a/src/aiogram_dialog/context/media_storage.py +++ b/src/aiogram_dialog/context/media_storage.py @@ -1,3 +1,4 @@ +from pathlib import Path from typing import Optional from aiogram.types import ContentType @@ -19,7 +20,15 @@ async def get_media_id( ) -> Optional[MediaId]: if not path and not url: return None - return self.cache.get((path, url, type)) + return self.cache.get((path, url, type, self._get_file_mtime(path))) + + def _get_file_mtime(self, path: Optional[str]) -> Optional[float]: + if not path: + return None + p = Path(path) + if p.exists(): + return None + return Path(path).stat().st_mtime async def save_media_id( self, From bf6756356b02642be6fc6205d541bb72eb55716d Mon Sep 17 00:00:00 2001 From: KurosawaAngel <145038102+KurosawaAngel@users.noreply.github.com> Date: Tue, 21 Jan 2025 17:13:56 +0500 Subject: [PATCH 2/7] fix tests --- src/aiogram_dialog/context/media_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aiogram_dialog/context/media_storage.py b/src/aiogram_dialog/context/media_storage.py index 0263ffd1..218fe72a 100644 --- a/src/aiogram_dialog/context/media_storage.py +++ b/src/aiogram_dialog/context/media_storage.py @@ -26,7 +26,7 @@ def _get_file_mtime(self, path: Optional[str]) -> Optional[float]: if not path: return None p = Path(path) - if p.exists(): + if not p.exists(): return None return Path(path).stat().st_mtime From ecb1d2380eaee5e7d2442384068f2d61cdfd9c18 Mon Sep 17 00:00:00 2001 From: KurosawaAngel <145038102+KurosawaAngel@users.noreply.github.com> Date: Tue, 21 Jan 2025 17:16:01 +0500 Subject: [PATCH 3/7] save mtime to cache --- src/aiogram_dialog/context/media_storage.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aiogram_dialog/context/media_storage.py b/src/aiogram_dialog/context/media_storage.py index 218fe72a..2e82449f 100644 --- a/src/aiogram_dialog/context/media_storage.py +++ b/src/aiogram_dialog/context/media_storage.py @@ -39,4 +39,4 @@ async def save_media_id( ) -> None: if not path and not url: return - self.cache[(path, url, type)] = media_id + self.cache[(path, url, type, self._get_file_mtime(path))] = media_id From 7607ce960b5872f36a21ff34a1e52574cd22df4d Mon Sep 17 00:00:00 2001 From: KurosawaAngel <145038102+KurosawaAngel@users.noreply.github.com> Date: Tue, 21 Jan 2025 17:38:58 +0500 Subject: [PATCH 4/7] fix saving each version --- src/aiogram_dialog/context/media_storage.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/aiogram_dialog/context/media_storage.py b/src/aiogram_dialog/context/media_storage.py index 2e82449f..f893781c 100644 --- a/src/aiogram_dialog/context/media_storage.py +++ b/src/aiogram_dialog/context/media_storage.py @@ -1,4 +1,4 @@ -from pathlib import Path +import os from typing import Optional from aiogram.types import ContentType @@ -20,15 +20,19 @@ async def get_media_id( ) -> Optional[MediaId]: if not path and not url: return None - return self.cache.get((path, url, type, self._get_file_mtime(path))) + cached = self.cache.get((path, url, type)) + if cached[1] is not None: + mtime = self._get_file_mtime(path) + if mtime is not None and mtime > cached[1]: + return None + return cached[0] def _get_file_mtime(self, path: Optional[str]) -> Optional[float]: if not path: return None - p = Path(path) - if not p.exists(): + if not os.path.exists(path): # noqa: PTH110 return None - return Path(path).stat().st_mtime + return os.path.getmtime(path) # noqa: PTH204 async def save_media_id( self, @@ -39,4 +43,4 @@ async def save_media_id( ) -> None: if not path and not url: return - self.cache[(path, url, type, self._get_file_mtime(path))] = media_id + self.cache[(path, url, type)] = (media_id, self._get_file_mtime(path)) From 9d7c1df9aea6b627e4584468a92ba865ef520d39 Mon Sep 17 00:00:00 2001 From: KurosawaAngel <145038102+KurosawaAngel@users.noreply.github.com> Date: Tue, 21 Jan 2025 18:01:08 +0500 Subject: [PATCH 5/7] staging --- src/aiogram_dialog/context/media_storage.py | 2 + tests/widgets/media/test_media_storage.py | 44 +++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 tests/widgets/media/test_media_storage.py diff --git a/src/aiogram_dialog/context/media_storage.py b/src/aiogram_dialog/context/media_storage.py index f893781c..1e27740c 100644 --- a/src/aiogram_dialog/context/media_storage.py +++ b/src/aiogram_dialog/context/media_storage.py @@ -21,6 +21,8 @@ async def get_media_id( if not path and not url: return None cached = self.cache.get((path, url, type)) + if cached is None: + return None if cached[1] is not None: mtime = self._get_file_mtime(path) if mtime is not None and mtime > cached[1]: diff --git a/tests/widgets/media/test_media_storage.py b/tests/widgets/media/test_media_storage.py new file mode 100644 index 00000000..3038088e --- /dev/null +++ b/tests/widgets/media/test_media_storage.py @@ -0,0 +1,44 @@ +import os +import tempfile + +import pytest +from aiogram.enums import ContentType + +from aiogram_dialog.context.media_storage import MediaIdStorage + + +@pytest.mark.asyncio +async def test_get_media_id(): + manager = MediaIdStorage() + with tempfile.NamedTemporaryFile() as file: + os.fsync(file) + media_id = await manager.get_media_id( + file.name, + None, + ContentType.DOCUMENT, + ) + assert media_id is None + + await manager.save_media_id( + file.name, + None, + ContentType.DOCUMENT, + "test1", + ) + + media_id = await manager.get_media_id( + file.name, + None, + ContentType.DOCUMENT, + ) + assert media_id == "test1" + + file.write(b"new info") + file.flush() + + media_id = await manager.get_media_id( + file.name, + None, + ContentType.DOCUMENT, + ) + assert media_id is None From b3fab2d56d175d1dd6ba62dc29d0d46103dc1114 Mon Sep 17 00:00:00 2001 From: KurosawaAngel <145038102+KurosawaAngel@users.noreply.github.com> Date: Wed, 22 Jan 2025 09:53:44 +0500 Subject: [PATCH 6/7] add tests --- src/aiogram_dialog/context/media_storage.py | 2 +- tests/widgets/media/test_media_storage.py | 22 +++++++++++++-------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/src/aiogram_dialog/context/media_storage.py b/src/aiogram_dialog/context/media_storage.py index 1e27740c..40425b3a 100644 --- a/src/aiogram_dialog/context/media_storage.py +++ b/src/aiogram_dialog/context/media_storage.py @@ -25,7 +25,7 @@ async def get_media_id( return None if cached[1] is not None: mtime = self._get_file_mtime(path) - if mtime is not None and mtime > cached[1]: + if mtime is not None and mtime != cached[1]: return None return cached[0] diff --git a/tests/widgets/media/test_media_storage.py b/tests/widgets/media/test_media_storage.py index 3038088e..83b6ae0e 100644 --- a/tests/widgets/media/test_media_storage.py +++ b/tests/widgets/media/test_media_storage.py @@ -1,3 +1,4 @@ +import asyncio import os import tempfile @@ -10,34 +11,39 @@ @pytest.mark.asyncio async def test_get_media_id(): manager = MediaIdStorage() - with tempfile.NamedTemporaryFile() as file: - os.fsync(file) + with tempfile.TemporaryDirectory() as d: + filename = os.path.join(d, "file_test") # noqa: PTH118 media_id = await manager.get_media_id( - file.name, + filename, None, ContentType.DOCUMENT, ) assert media_id is None + with open(filename, "w") as file: # noqa: PTH123 + file.write("test1") + await manager.save_media_id( - file.name, + filename, None, ContentType.DOCUMENT, "test1", ) media_id = await manager.get_media_id( - file.name, + filename, None, ContentType.DOCUMENT, ) assert media_id == "test1" - file.write(b"new info") - file.flush() + await asyncio.sleep(0.1) + + with open(filename, "w") as file: # noqa: PTH123 + file.write("test2") media_id = await manager.get_media_id( - file.name, + filename, None, ContentType.DOCUMENT, ) From 99b8d88d002e4fe739c01c2602c4d79dc1564cde Mon Sep 17 00:00:00 2001 From: KurosawaAngel <145038102+KurosawaAngel@users.noreply.github.com> Date: Wed, 22 Jan 2025 17:46:08 +0500 Subject: [PATCH 7/7] named tuple for cached media id --- src/aiogram_dialog/context/media_storage.py | 24 +++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/src/aiogram_dialog/context/media_storage.py b/src/aiogram_dialog/context/media_storage.py index 40425b3a..3af4a84a 100644 --- a/src/aiogram_dialog/context/media_storage.py +++ b/src/aiogram_dialog/context/media_storage.py @@ -1,5 +1,5 @@ import os -from typing import Optional +from typing import NamedTuple, Optional, cast from aiogram.types import ContentType from cachetools import LRUCache @@ -8,6 +8,11 @@ from aiogram_dialog.api.protocols import MediaIdStorageProtocol +class CachedMediaId(NamedTuple): + media_id: MediaId + mtime: Optional[float] + + class MediaIdStorage(MediaIdStorageProtocol): def __init__(self, maxsize=10240): self.cache = LRUCache(maxsize=maxsize) @@ -20,14 +25,18 @@ async def get_media_id( ) -> Optional[MediaId]: if not path and not url: return None - cached = self.cache.get((path, url, type)) + cached = cast( + Optional[CachedMediaId], + self.cache.get((path, url, type)), + ) if cached is None: return None - if cached[1] is not None: + + if cached.mtime is not None: mtime = self._get_file_mtime(path) - if mtime is not None and mtime != cached[1]: + if mtime is not None and mtime != cached.mtime: return None - return cached[0] + return cached.media_id def _get_file_mtime(self, path: Optional[str]) -> Optional[float]: if not path: @@ -45,4 +54,7 @@ async def save_media_id( ) -> None: if not path and not url: return - self.cache[(path, url, type)] = (media_id, self._get_file_mtime(path)) + self.cache[(path, url, type)] = CachedMediaId( + media_id, + self._get_file_mtime(path), + )