details:   https://code.tryton.org/tryton/commit/a7acadd009dd
branch:    default
user:      Nicolas Évrard <[email protected]>
date:      Thu Jan 09 18:53:09 2025 +0100
description:
        Add deletion on FileStore

        We're identifying each file in the filestore with a UUID4 in order to 
avoid
        collisions. It has the drawback that we will store twice the same file 
but on
        the other hand we can now remove each file from the store as there is 
no more
        collision.

        In order to identify files named previously we're using textual 
representation
        of the UUID with the hyphens. Previous IDs do not have hyphens.
diffstat:

 trytond/CHANGELOG                       |   1 +
 trytond/doc/ref/filestore.rst           |   9 ++++++++
 trytond/trytond/filestore.py            |  37 ++++++++++++++++++--------------
 trytond/trytond/tests/test_filestore.py |  34 ++++++++++++++----------------
 4 files changed, 47 insertions(+), 34 deletions(-)

diffs (140 lines):

diff -r de353f5eef00 -r a7acadd009dd trytond/CHANGELOG
--- a/trytond/CHANGELOG Tue Feb 17 18:10:15 2026 +0100
+++ b/trytond/CHANGELOG Thu Jan 09 18:53:09 2025 +0100
@@ -1,3 +1,4 @@
+* Add delete and delete_many to Filestore
 * Deprecate ``grouped_slice`` without size
 * Replace ``Database.IN_MAX`` by ``backend.MAX_QUERY_PARAMS``
 * Deprecate ``reduce_ids`` for ``SQL_OPERATORS['in']``
diff -r de353f5eef00 -r a7acadd009dd trytond/doc/ref/filestore.rst
--- a/trytond/doc/ref/filestore.rst     Tue Feb 17 18:10:15 2026 +0100
+++ b/trytond/doc/ref/filestore.rst     Thu Jan 09 18:53:09 2025 +0100
@@ -37,6 +37,15 @@
 
    Store the sequence of data and return a list of identifiers.
 
+.. method:: FileStore.delete(id[, prefix])
+
+   Remove the data identified by ``id`` in the prefixed directory.
+
+.. method:: FileStore.delete_many(ids[, prefix])
+
+   Remove the sequence of data identified by the sequence of ``ids`` in the
+   prefixed directory.
+
 .. note::
    The class can be overridden by setting a fully qualified name of a
    alternative class defined in the configuration ``class`` of the ``database``
diff -r de353f5eef00 -r a7acadd009dd trytond/trytond/filestore.py
--- a/trytond/trytond/filestore.py      Tue Feb 17 18:10:15 2026 +0100
+++ b/trytond/trytond/filestore.py      Thu Jan 09 18:53:09 2025 +0100
@@ -1,7 +1,8 @@
 # This file is part of Tryton.  The COPYRIGHT file at the top level of
 # this repository contains the full copyright notices and license terms.
-import hashlib
+
 import os
+import uuid
 from functools import cache
 
 import trytond.config as config
@@ -38,20 +39,9 @@
         filename = self._filename(id, prefix)
         dirname = os.path.dirname(filename)
         os.makedirs(dirname, mode=0o770, exist_ok=True)
-
-        collision = 0
-        while True:
-            basename = os.path.basename(filename)
-            if os.path.exists(filename):
-                if data != self.get(basename, prefix):
-                    collision += 1
-                    filename = self._filename(
-                        '%s-%s' % (id, collision), prefix)
-                    continue
-            else:
-                with open(filename, 'wb')as fp:
-                    fp.write(data)
-            return basename
+        with open(filename, 'wb')as fp:
+            fp.write(data)
+        return os.path.basename(filename)
 
     def setmany(self, data, prefix=''):
         return [self.set(d, prefix) for d in data]
@@ -69,7 +59,22 @@
         return filename
 
     def _id(self, data):
-        return hashlib.md5(data).hexdigest()
+        return str(uuid.uuid4())
+
+    def delete(self, id, prefix=''):
+        # Older file ids didn't have a dash in the filename. We want to keep
+        # those as we don't know which resource might point to them.
+        if '-' not in id:
+            return
+
+        try:
+            os.remove(self._filename(id, prefix))
+        except FileNotFoundError:
+            pass
+
+    def delete_many(self, ids, prefix=''):
+        for id_ in ids:
+            self.delete(id_, prefix)
 
 
 if config.get('database', 'class'):
diff -r de353f5eef00 -r a7acadd009dd trytond/trytond/tests/test_filestore.py
--- a/trytond/trytond/tests/test_filestore.py   Tue Feb 17 18:10:15 2026 +0100
+++ b/trytond/trytond/tests/test_filestore.py   Thu Jan 09 18:53:09 2025 +0100
@@ -3,7 +3,6 @@
 import os
 import shutil
 import tempfile
-from unittest.mock import patch
 
 import trytond.config as config
 from trytond.filestore import filestore
@@ -73,24 +72,23 @@
         with self.assertRaises(ValueError):
             filestore.set(self.data(), prefix='../')
 
-    def test_duplicate(self):
-        "Test duplicate"
+    def test_delete(self):
+        "Test deletion"
         data = self.data()
-        id = filestore.set(data, prefix='test')
-        self.assertEqual(filestore.set(data, prefix='test'), id)
-
-    def test_collision(self):
-        "Test collision"
-        data1 = self.data()
-        data2 = self.data()
+        id_ = filestore.set(data, prefix='test')
+        filestore.delete(id_, prefix='test')
+        with self.assertRaises(IOError):
+            filestore.get(id_, prefix='test')
 
-        id1 = filestore.set(data1, prefix='test')
-
-        with patch.object(filestore, '_id') as _id:
-            _id.return_value = id1
+    def test_delete_many(self):
+        "Test multiple deletions"
+        id1, id2, id3 = filestore.setmany(
+            [self.data(), self.data(), self.data()],
+            prefix='test')
+        filestore.delete_many([id1, id2], prefix='test')
 
-            id2 = filestore.set(data2, prefix='test')
+        for id_ in [id1, id2]:
+            with self.assertRaises(IOError):
+                filestore.get(id_, prefix='test')
 
-        self.assertNotEqual(id1, id2)
-        self.assertEqual(filestore.get(id1, prefix='test'), data1)
-        self.assertEqual(filestore.get(id2, prefix='test'), data2)
+        self.assertTrue(filestore.get(id3, prefix='test'))

Reply via email to