From 3656a218073991a3e5400b912f3d76d638fe5154 Mon Sep 17 00:00:00 2001 From: Francisco Ramirez Date: Wed, 1 Sep 2021 16:03:35 +0000 Subject: [PATCH 1/4] Preparing RepositoryBackend classes * Added has_objects as new abstract and apted has_object * Added delete_objects as new abstract and adapted delete_object * Added list_objects method * While modifying the inherited classes I removed some of the docstrings since these should be anyways inherited from the parent class and were just adding visual noise and maintainance costs. --- aiida/backends/general/migrations/utils.py | 31 ++++------- aiida/repository/backend/abstract.py | 52 +++++++++++++++---- aiida/repository/backend/disk_object_store.py | 31 +++++------ aiida/repository/backend/sandbox.py | 38 +++++++------- tests/repository/backend/test_abstract.py | 16 ++++-- 5 files changed, 97 insertions(+), 71 deletions(-) diff --git a/aiida/backends/general/migrations/utils.py b/aiida/backends/general/migrations/utils.py index 734f40839a..ac429c43ca 100644 --- a/aiida/backends/general/migrations/utils.py +++ b/aiida/backends/general/migrations/utils.py @@ -15,7 +15,8 @@ import os import pathlib import re -import typing + +from typing import Union, Optional, Dict, List, Iterable from disk_objectstore import Container from disk_objectstore.utils import LazyOpener @@ -42,8 +43,8 @@ def __init__( self, name: str = '', file_type: FileType = FileType.DIRECTORY, - key: typing.Union[str, None, LazyOpener] = None, - objects: typing.Dict[str, 'File'] = None + key: Union[str, None, LazyOpener] = None, + objects: Dict[str, 'File'] = None ): # pylint: disable=super-init-not-called if not isinstance(name, str): @@ -86,7 +87,7 @@ class NoopRepositoryBackend(AbstractRepositoryBackend): """ @property - def uuid(self) -> typing.Optional[str]: + def uuid(self) -> Optional[str]: """Return the unique identifier of the repository. .. note:: A sandbox folder does not have the concept of a unique identifier and so always returns ``None``. @@ -94,35 +95,25 @@ def uuid(self) -> typing.Optional[str]: return None def initialise(self, **kwargs) -> None: - """Initialise the repository if it hasn't already been initialised. - - :param kwargs: parameters for the initialisation. - """ raise NotImplementedError() @property def is_initialised(self) -> bool: - """Return whether the repository has been initialised.""" return True def erase(self): raise NotImplementedError() def _put_object_from_filelike(self, handle: io.BufferedIOBase) -> str: - """Store the byte contents of a file in the repository. - - :param handle: filelike object with the byte content to be stored. - :return: the generated fully qualified identifier for the object within the repository. - :raises TypeError: if the handle is not a byte stream. - """ return LazyOpener(handle.name) - def has_object(self, key: str) -> bool: - """Return whether the repository has an object with the given key. + def has_objects(self, keys: List[str]) -> List[bool]: + raise NotImplementedError() - :param key: fully qualified identifier for the object within the repository. - :return: True if the object exists, False otherwise. - """ + def delete_objects(self, keys: List[str]) -> None: + raise NotImplementedError() + + def list_objects(self) -> Iterable[str]: raise NotImplementedError() diff --git a/aiida/repository/backend/abstract.py b/aiida/repository/backend/abstract.py index 9a3527c97a..743d3f8016 100644 --- a/aiida/repository/backend/abstract.py +++ b/aiida/repository/backend/abstract.py @@ -10,7 +10,8 @@ import hashlib import io import pathlib -import typing + +from typing import Union, Optional, Iterator, BinaryIO, List, Iterable from aiida.common.hashing import chunked_file_hash @@ -30,7 +31,7 @@ class AbstractRepositoryBackend(metaclass=abc.ABCMeta): @property @abc.abstractmethod - def uuid(self) -> typing.Optional[str]: + def uuid(self) -> Optional[str]: """Return the unique identifier of the repository.""" @abc.abstractmethod @@ -58,7 +59,7 @@ def erase(self) -> None: def is_readable_byte_stream(handle) -> bool: return hasattr(handle, 'read') and hasattr(handle, 'mode') and 'b' in handle.mode - def put_object_from_filelike(self, handle: typing.BinaryIO) -> str: + def put_object_from_filelike(self, handle: BinaryIO) -> str: """Store the byte contents of a file in the repository. :param handle: filelike object with the byte content to be stored. @@ -70,10 +71,10 @@ def put_object_from_filelike(self, handle: typing.BinaryIO) -> str: return self._put_object_from_filelike(handle) @abc.abstractmethod - def _put_object_from_filelike(self, handle: typing.BinaryIO) -> str: + def _put_object_from_filelike(self, handle: BinaryIO) -> str: pass - def put_object_from_file(self, filepath: typing.Union[str, pathlib.Path]) -> str: + def put_object_from_file(self, filepath: Union[str, pathlib.Path]) -> str: """Store a new object with contents of the file located at `filepath` on this file system. :param filepath: absolute path of file whose contents to copy to the repository. @@ -84,15 +85,33 @@ def put_object_from_file(self, filepath: typing.Union[str, pathlib.Path]) -> str return self.put_object_from_filelike(handle) @abc.abstractmethod + def has_objects(self, keys: List[str]) -> List[bool]: + """Return whether the repository has an object with the given key. + + :param keys: + list of fully qualified identifiers for objects within the repository. + :return: + list of logicals, in the same order as the keys provided, with value True if the respective + object exists and False otherwise. + """ + def has_object(self, key: str) -> bool: """Return whether the repository has an object with the given key. :param key: fully qualified identifier for the object within the repository. :return: True if the object exists, False otherwise. """ + return self.has_objects([key])[0] + + @abc.abstractmethod + def list_objects(self) -> Iterable[str]: + """Return iterable that yeilds all available objects by key. + + :return: An iterable for all the available object keys. + """ @contextlib.contextmanager - def open(self, key: str) -> typing.Iterator[typing.BinaryIO]: + def open(self, key: str) -> Iterator[BinaryIO]: """Open a file handle to an object stored under the given key. .. note:: this should only be used to open a handle to read an existing file. To write a new file use the method @@ -130,12 +149,27 @@ def get_object_hash(self, key: str) -> str: with self.open(key) as handle: # pylint: disable=not-context-manager return chunked_file_hash(handle, hashlib.sha256) - def delete_object(self, key: str): + @abc.abstractmethod + def delete_objects(self, keys: List[str]) -> None: + """Delete the objects from the repository. + + :param keys: list of fully qualified identifiers for the objects within the repository. + :raise FileNotFoundError: if any of the files does not exist. + :raise OSError: if any of the files could not be deleted. + """ + keys_exist = self.has_objects(keys) + if not all(keys_exist): + error_message = 'some of the keys provided do not correspond to any object in the repository:\n' + for indx, key_exists in enumerate(keys_exist): + if not key_exists: + error_message += f' > object with key `{keys[indx]}` does not exist.\n' + raise FileNotFoundError(error_message) + + def delete_object(self, key: str) -> None: """Delete the object from the repository. :param key: fully qualified identifier for the object within the repository. :raise FileNotFoundError: if the file does not exist. :raise OSError: if the file could not be deleted. """ - if not self.has_object(key): - raise FileNotFoundError(f'object with key `{key}` does not exist.') + return self.delete_objects([key]) diff --git a/aiida/repository/backend/disk_object_store.py b/aiida/repository/backend/disk_object_store.py index c4c5306317..9c372a03b9 100644 --- a/aiida/repository/backend/disk_object_store.py +++ b/aiida/repository/backend/disk_object_store.py @@ -2,7 +2,8 @@ """Implementation of the ``AbstractRepositoryBackend`` using the ``disk-objectstore`` as the backend.""" import contextlib import shutil -import typing + +from typing import Optional, Iterator, BinaryIO, List, Iterable from disk_objectstore import Container @@ -27,7 +28,7 @@ def __str__(self) -> str: return 'DiskObjectStoreRepository: ' @property - def uuid(self) -> typing.Optional[str]: + def uuid(self) -> Optional[str]: """Return the unique identifier of the repository.""" if not self.is_initialised: return None @@ -56,7 +57,7 @@ def erase(self): except FileNotFoundError: pass - def _put_object_from_filelike(self, handle: typing.BinaryIO) -> str: + def _put_object_from_filelike(self, handle: BinaryIO) -> str: """Store the byte contents of a file in the repository. :param handle: filelike object with the byte content to be stored. @@ -65,16 +66,11 @@ def _put_object_from_filelike(self, handle: typing.BinaryIO) -> str: """ return self.container.add_object(handle.read()) - def has_object(self, key: str) -> bool: - """Return whether the repository has an object with the given key. - - :param key: fully qualified identifier for the object within the repository. - :return: True if the object exists, False otherwise. - """ - return self.container.has_object(key) + def has_objects(self, keys: List[str]) -> List[bool]: + return self.container.has_objects(keys) @contextlib.contextmanager - def open(self, key: str) -> typing.Iterator[typing.BinaryIO]: + def open(self, key: str) -> Iterator[BinaryIO]: """Open a file handle to an object stored under the given key. .. note:: this should only be used to open a handle to read an existing file. To write a new file use the method @@ -90,15 +86,12 @@ def open(self, key: str) -> typing.Iterator[typing.BinaryIO]: with self.container.get_object_stream(key) as handle: yield handle # type: ignore[misc] - def delete_object(self, key: str): - """Delete the object from the repository. + def delete_objects(self, keys: List[str]) -> None: + super().delete_objects(keys) + self.container.delete_objects(keys) - :param key: fully qualified identifier for the object within the repository. - :raise FileNotFoundError: if the file does not exist. - :raise OSError: if the file could not be deleted. - """ - super().delete_object(key) - self.container.delete_objects([key]) + def list_objects(self) -> Iterable[str]: + return self.container.list_all_objects() def get_object_hash(self, key: str) -> str: """Return the SHA-256 hash of an object stored under the given key. diff --git a/aiida/repository/backend/sandbox.py b/aiida/repository/backend/sandbox.py index f4577f4783..7bded0dd5b 100644 --- a/aiida/repository/backend/sandbox.py +++ b/aiida/repository/backend/sandbox.py @@ -3,9 +3,10 @@ import contextlib import os import shutil -import typing import uuid +from typing import Optional, Iterator, BinaryIO, List, Iterable + from .abstract import AbstractRepositoryBackend __all__ = ('SandboxRepositoryBackend',) @@ -16,7 +17,7 @@ class SandboxRepositoryBackend(AbstractRepositoryBackend): def __init__(self): from aiida.common.folders import SandboxFolder - self._sandbox: typing.Optional[SandboxFolder] = None + self._sandbox: Optional[SandboxFolder] = None def __str__(self) -> str: """Return the string representation of this repository.""" @@ -29,7 +30,7 @@ def __del__(self): self.erase() @property - def uuid(self) -> typing.Optional[str]: + def uuid(self) -> Optional[str]: """Return the unique identifier of the repository. .. note:: A sandbox folder does not have the concept of a unique identifier and so always returns ``None``. @@ -70,7 +71,7 @@ def erase(self): finally: self._sandbox = None - def _put_object_from_filelike(self, handle: typing.BinaryIO) -> str: + def _put_object_from_filelike(self, handle: BinaryIO) -> str: """Store the byte contents of a file in the repository. :param handle: filelike object with the byte content to be stored. @@ -85,16 +86,15 @@ def _put_object_from_filelike(self, handle: typing.BinaryIO) -> str: return key - def has_object(self, key: str) -> bool: - """Return whether the repository has an object with the given key. - - :param key: fully qualified identifier for the object within the repository. - :return: True if the object exists, False otherwise. - """ - return key in os.listdir(self.sandbox.abspath) + def has_objects(self, keys: List[str]) -> List[bool]: + result = list() + dirlist = os.listdir(self.sandbox.abspath) + for key in keys: + result.append(key in dirlist) + return result @contextlib.contextmanager - def open(self, key: str) -> typing.Iterator[typing.BinaryIO]: + def open(self, key: str) -> Iterator[BinaryIO]: """Open a file handle to an object stored under the given key. .. note:: this should only be used to open a handle to read an existing file. To write a new file use the method @@ -110,12 +110,10 @@ def open(self, key: str) -> typing.Iterator[typing.BinaryIO]: with self.sandbox.open(key, mode='rb') as handle: yield handle - def delete_object(self, key: str): - """Delete the object from the repository. + def delete_objects(self, keys: List[str]) -> None: + super().delete_objects(keys) + for key in keys: + os.remove(os.path.join(self.sandbox.abspath, key)) - :param key: fully qualified identifier for the object within the repository. - :raise FileNotFoundError: if the file does not exist. - :raise OSError: if the file could not be deleted. - """ - super().delete_object(key) - os.remove(os.path.join(self.sandbox.abspath, key)) + def list_objects(self) -> Iterable[str]: + return self.sandbox.get_content_list() diff --git a/tests/repository/backend/test_abstract.py b/tests/repository/backend/test_abstract.py index 3a548bd515..2a64ddfc7e 100644 --- a/tests/repository/backend/test_abstract.py +++ b/tests/repository/backend/test_abstract.py @@ -3,7 +3,8 @@ """Tests for the :mod:`aiida.repository.backend.abstract` module.""" import io import tempfile -import typing + +from typing import Optional, BinaryIO, List, Iterable import pytest @@ -17,7 +18,7 @@ def has_object(self, key): return True @property - def uuid(self) -> typing.Optional[str]: + def uuid(self) -> Optional[str]: return None def initialise(self, **kwargs) -> None: @@ -30,7 +31,16 @@ def erase(self): def is_initialised(self) -> bool: return True - def _put_object_from_filelike(self, handle: typing.BinaryIO) -> str: + def _put_object_from_filelike(self, handle: BinaryIO) -> str: + pass + + def delete_objects(self, keys: List[str]) -> None: + pass + + def has_objects(self, keys: List[str]) -> List[bool]: + pass + + def list_objects(self) -> Iterable[str]: pass From a2b53257a1cd94dc31bc9f0c686664d5963727b3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 29 Sep 2021 08:28:54 +0000 Subject: [PATCH 2/4] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- aiida/backends/general/migrations/utils.py | 3 +-- aiida/repository/backend/abstract.py | 3 +-- aiida/repository/backend/disk_object_store.py | 3 +-- aiida/repository/backend/sandbox.py | 3 +-- tests/repository/backend/test_abstract.py | 3 +-- 5 files changed, 5 insertions(+), 10 deletions(-) diff --git a/aiida/backends/general/migrations/utils.py b/aiida/backends/general/migrations/utils.py index ac429c43ca..579f78ab0a 100644 --- a/aiida/backends/general/migrations/utils.py +++ b/aiida/backends/general/migrations/utils.py @@ -15,8 +15,7 @@ import os import pathlib import re - -from typing import Union, Optional, Dict, List, Iterable +from typing import Dict, Iterable, List, Optional, Union from disk_objectstore import Container from disk_objectstore.utils import LazyOpener diff --git a/aiida/repository/backend/abstract.py b/aiida/repository/backend/abstract.py index 743d3f8016..6f39615946 100644 --- a/aiida/repository/backend/abstract.py +++ b/aiida/repository/backend/abstract.py @@ -10,8 +10,7 @@ import hashlib import io import pathlib - -from typing import Union, Optional, Iterator, BinaryIO, List, Iterable +from typing import BinaryIO, Iterable, Iterator, List, Optional, Union from aiida.common.hashing import chunked_file_hash diff --git a/aiida/repository/backend/disk_object_store.py b/aiida/repository/backend/disk_object_store.py index 9c372a03b9..9124a612c7 100644 --- a/aiida/repository/backend/disk_object_store.py +++ b/aiida/repository/backend/disk_object_store.py @@ -2,8 +2,7 @@ """Implementation of the ``AbstractRepositoryBackend`` using the ``disk-objectstore`` as the backend.""" import contextlib import shutil - -from typing import Optional, Iterator, BinaryIO, List, Iterable +from typing import BinaryIO, Iterable, Iterator, List, Optional from disk_objectstore import Container diff --git a/aiida/repository/backend/sandbox.py b/aiida/repository/backend/sandbox.py index 7bded0dd5b..c65d445d19 100644 --- a/aiida/repository/backend/sandbox.py +++ b/aiida/repository/backend/sandbox.py @@ -3,10 +3,9 @@ import contextlib import os import shutil +from typing import BinaryIO, Iterable, Iterator, List, Optional import uuid -from typing import Optional, Iterator, BinaryIO, List, Iterable - from .abstract import AbstractRepositoryBackend __all__ = ('SandboxRepositoryBackend',) diff --git a/tests/repository/backend/test_abstract.py b/tests/repository/backend/test_abstract.py index 2a64ddfc7e..bf3667c994 100644 --- a/tests/repository/backend/test_abstract.py +++ b/tests/repository/backend/test_abstract.py @@ -3,8 +3,7 @@ """Tests for the :mod:`aiida.repository.backend.abstract` module.""" import io import tempfile - -from typing import Optional, BinaryIO, List, Iterable +from typing import BinaryIO, Iterable, List, Optional import pytest From f19f1e87b85177414fc1c696474e3dcd202f544d Mon Sep 17 00:00:00 2001 From: Francisco Ramirez Date: Thu, 30 Sep 2021 11:46:12 +0200 Subject: [PATCH 3/4] Update aiida/repository/backend/disk_object_store.py Co-authored-by: Chris Sewell --- aiida/repository/backend/disk_object_store.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aiida/repository/backend/disk_object_store.py b/aiida/repository/backend/disk_object_store.py index 9124a612c7..b09c7c0213 100644 --- a/aiida/repository/backend/disk_object_store.py +++ b/aiida/repository/backend/disk_object_store.py @@ -63,7 +63,7 @@ def _put_object_from_filelike(self, handle: BinaryIO) -> str: :return: the generated fully qualified identifier for the object within the repository. :raises TypeError: if the handle is not a byte stream. """ - return self.container.add_object(handle.read()) + return self.container.add_streamed_object(handle) def has_objects(self, keys: List[str]) -> List[bool]: return self.container.has_objects(keys) From c2c64b183a4f4ba88e8539b086fbfbeab096797a Mon Sep 17 00:00:00 2001 From: ramirezfranciscof Date: Mon, 4 Oct 2021 10:47:47 +0000 Subject: [PATCH 4/4] Add tests + PR requests --- aiida/backends/general/migrations/utils.py | 4 ++ aiida/repository/backend/abstract.py | 12 ++++- aiida/repository/backend/disk_object_store.py | 4 ++ aiida/repository/backend/sandbox.py | 4 ++ tests/repository/backend/test_abstract.py | 48 ++++++++++++++++--- .../backend/test_disk_object_store.py | 22 +++++++++ tests/repository/backend/test_sandbox.py | 22 +++++++++ 7 files changed, 109 insertions(+), 7 deletions(-) diff --git a/aiida/backends/general/migrations/utils.py b/aiida/backends/general/migrations/utils.py index 579f78ab0a..e947276282 100644 --- a/aiida/backends/general/migrations/utils.py +++ b/aiida/backends/general/migrations/utils.py @@ -93,6 +93,10 @@ def uuid(self) -> Optional[str]: """ return None + @property + def key_format(self) -> Optional[str]: + return None + def initialise(self, **kwargs) -> None: raise NotImplementedError() diff --git a/aiida/repository/backend/abstract.py b/aiida/repository/backend/abstract.py index 6f39615946..20621fb584 100644 --- a/aiida/repository/backend/abstract.py +++ b/aiida/repository/backend/abstract.py @@ -33,6 +33,16 @@ class AbstractRepositoryBackend(metaclass=abc.ABCMeta): def uuid(self) -> Optional[str]: """Return the unique identifier of the repository.""" + @property + @abc.abstractmethod + def key_format(self) -> Optional[str]: + """Return the format for the keys of the repository. + + Important for when migrating between backends (e.g. archive -> main), as if they are not equal then it is + necessary to re-compute all the `Node.repository_metadata` before importing (otherwise they will not match + with the repository). + """ + @abc.abstractmethod def initialise(self, **kwargs) -> None: """Initialise the repository if it hasn't already been initialised. @@ -104,7 +114,7 @@ def has_object(self, key: str) -> bool: @abc.abstractmethod def list_objects(self) -> Iterable[str]: - """Return iterable that yeilds all available objects by key. + """Return iterable that yields all available objects by key. :return: An iterable for all the available object keys. """ diff --git a/aiida/repository/backend/disk_object_store.py b/aiida/repository/backend/disk_object_store.py index b09c7c0213..387475aff3 100644 --- a/aiida/repository/backend/disk_object_store.py +++ b/aiida/repository/backend/disk_object_store.py @@ -33,6 +33,10 @@ def uuid(self) -> Optional[str]: return None return self.container.container_id + @property + def key_format(self) -> Optional[str]: + return self.container.hash_type + def initialise(self, **kwargs) -> None: """Initialise the repository if it hasn't already been initialised. diff --git a/aiida/repository/backend/sandbox.py b/aiida/repository/backend/sandbox.py index c65d445d19..719ff6f7bb 100644 --- a/aiida/repository/backend/sandbox.py +++ b/aiida/repository/backend/sandbox.py @@ -36,6 +36,10 @@ def uuid(self) -> Optional[str]: """ return None + @property + def key_format(self) -> Optional[str]: + return 'uuid4' + def initialise(self, **kwargs) -> None: """Initialise the repository if it hasn't already been initialised. diff --git a/tests/repository/backend/test_abstract.py b/tests/repository/backend/test_abstract.py index bf3667c994..293d40fc61 100644 --- a/tests/repository/backend/test_abstract.py +++ b/tests/repository/backend/test_abstract.py @@ -13,13 +13,14 @@ class RepositoryBackend(AbstractRepositoryBackend): """Concrete implementation of ``AbstractRepositoryBackend``.""" - def has_object(self, key): - return True - @property def uuid(self) -> Optional[str]: return None + @property + def key_format(self) -> Optional[str]: + return None + def initialise(self, **kwargs) -> None: return @@ -33,11 +34,14 @@ def is_initialised(self) -> bool: def _put_object_from_filelike(self, handle: BinaryIO) -> str: pass - def delete_objects(self, keys: List[str]) -> None: - pass + # pylint useless-super-delegation needs to be disabled here because it refuses to + # recognize that this is an abstract method and thus has to be overriden. See the + # following issue: https://github.com/PyCQA/pylint/issues/1594 + def delete_objects(self, keys: List[str]) -> None: # pylint: disable=useless-super-delegation + super().delete_objects(keys) def has_objects(self, keys: List[str]) -> List[bool]: - pass + return [True] def list_objects(self) -> Iterable[str]: pass @@ -93,3 +97,35 @@ def test_put_object_from_file(repository, generate_directory): repository.put_object_from_file(directory / 'file_a') repository.put_object_from_file(str(directory / 'file_a')) + + +def test_passes_to_batch(repository, monkeypatch): + """Checks that the single object operations call the batch operations""" + + def mock_batch_operation(self, keys): + raise NotImplementedError('this method was intentionally not implemented') + + monkeypatch.setattr(RepositoryBackend, 'has_objects', mock_batch_operation) + with pytest.raises(NotImplementedError) as execinfo: + repository.has_object('object_key') + assert str(execinfo.value) == 'this method was intentionally not implemented' + + monkeypatch.undo() + + monkeypatch.setattr(RepositoryBackend, 'delete_objects', mock_batch_operation) + with pytest.raises(NotImplementedError) as execinfo: + repository.delete_object('object_key') + assert str(execinfo.value) == 'this method was intentionally not implemented' + + +def test_delete_objects_test(repository, monkeypatch): + """Checks that the super of delete_objects will check for existence of the files""" + + def has_objects_mock(self, keys): # pylint: disable=unused-argument + return [False for key in keys] + + monkeypatch.setattr(RepositoryBackend, 'has_objects', has_objects_mock) + with pytest.raises(FileNotFoundError) as execinfo: + repository.delete_objects(['object_key']) + assert 'exist' in str(execinfo.value) + assert 'object_key' in str(execinfo.value) diff --git a/tests/repository/backend/test_disk_object_store.py b/tests/repository/backend/test_disk_object_store.py index 8d668fb0ce..4d3b943d4c 100644 --- a/tests/repository/backend/test_disk_object_store.py +++ b/tests/repository/backend/test_disk_object_store.py @@ -152,3 +152,25 @@ def test_get_object_hash(repository, generate_directory): key = repository.put_object_from_filelike(handle) assert repository.get_object_hash(key) == 'ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73' + + +def test_list_objects(repository, generate_directory): + """Test the ``Repository.delete_object`` method.""" + repository.initialise() + keylist = list() + + directory = generate_directory({'file_a': b'content a'}) + with open(directory / 'file_a', 'rb') as handle: + keylist.append(repository.put_object_from_filelike(handle)) + + directory = generate_directory({'file_b': b'content b'}) + with open(directory / 'file_b', 'rb') as handle: + keylist.append(repository.put_object_from_filelike(handle)) + + assert sorted(list(repository.list_objects())) == sorted(keylist) + + +def test_key_format(repository): + """Test the ``key_format`` property.""" + repository.initialise() + assert repository.key_format == repository.container.hash_type diff --git a/tests/repository/backend/test_sandbox.py b/tests/repository/backend/test_sandbox.py index 3ef0694c5b..6828c58549 100644 --- a/tests/repository/backend/test_sandbox.py +++ b/tests/repository/backend/test_sandbox.py @@ -152,3 +152,25 @@ def test_get_object_hash(repository, generate_directory): key = repository.put_object_from_filelike(handle) assert repository.get_object_hash(key) == 'ed7002b439e9ac845f22357d822bac1444730fbdb6016d3ec9432297b9ec9f73' + + +def test_list_objects(repository, generate_directory): + """Test the ``Repository.delete_object`` method.""" + repository.initialise() + keylist = list() + + directory = generate_directory({'file_a': b'content a'}) + with open(directory / 'file_a', 'rb') as handle: + keylist.append(repository.put_object_from_filelike(handle)) + + directory = generate_directory({'file_b': b'content b'}) + with open(directory / 'file_b', 'rb') as handle: + keylist.append(repository.put_object_from_filelike(handle)) + + assert sorted(list(repository.list_objects())) == sorted(keylist) + + +def test_key_format(repository): + """Test the ``key_format`` property.""" + repository.initialise() + assert repository.key_format == 'uuid4'