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'))