From 5eac8b49df33287c3dc6cfbf46eae491c3196fc4 Mon Sep 17 00:00:00 2001 From: Sebastiaan Huber Date: Tue, 12 Dec 2023 14:54:07 +0100 Subject: [PATCH] `SqliteZipStorage`: Ensure the `filepath` is absolute and exists Otherwise, loading the storage will fail as soon as the working directory is changed. --- aiida/storage/sqlite_zip/backend.py | 11 ++++++++++- tests/storage/sqlite_zip/__init__.py | 2 ++ tests/storage/sqlite_zip/test_backend.py | 20 +++++++++++++++++++- 3 files changed, 31 insertions(+), 2 deletions(-) create mode 100644 tests/storage/sqlite_zip/__init__.py diff --git a/aiida/storage/sqlite_zip/backend.py b/aiida/storage/sqlite_zip/backend.py index 0c34a2405d..9bf6a31937 100644 --- a/aiida/storage/sqlite_zip/backend.py +++ b/aiida/storage/sqlite_zip/backend.py @@ -21,7 +21,7 @@ from zipfile import ZipFile, is_zipfile from archive_path import ZipPath, extract_file_in_zip -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, field_validator from sqlalchemy.orm import Session from aiida import __version__ @@ -66,12 +66,21 @@ class SqliteZipBackend(StorageBackend): # pylint: disable=too-many-public-metho """ class Configuration(BaseModel): + """Model describing required information to configure an instance of the storage.""" filepath: str = Field( title='Filepath of the archive', description='Filepath of the archive in which to store data for this backend.' ) + @field_validator('filepath') + @classmethod + def filepath_exists_and_is_absolute(cls, value: str) -> str: + """Validate the filepath exists and return the resolved and absolute filepath.""" + filepath = Path(value) + assert filepath.is_file(), f'The archive `{value}` does not exist.' + return str(filepath.resolve().absolute()) + _read_only = True @classmethod diff --git a/tests/storage/sqlite_zip/__init__.py b/tests/storage/sqlite_zip/__init__.py new file mode 100644 index 0000000000..74f7e7d5e6 --- /dev/null +++ b/tests/storage/sqlite_zip/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +"""Tests for :mod:`aiida.storage.sqlite_zip`.""" diff --git a/tests/storage/sqlite_zip/test_backend.py b/tests/storage/sqlite_zip/test_backend.py index f27c3a21b3..ed47741956 100644 --- a/tests/storage/sqlite_zip/test_backend.py +++ b/tests/storage/sqlite_zip/test_backend.py @@ -1,5 +1,10 @@ # -*- coding: utf-8 -*- -"""Tests for :mod:`aiida.storage.sqlite_zip.backend.SqliteZipBackend`.""" +"""Tests for :mod:`aiida.storage.sqlite_zip.backend`.""" +import pathlib + +from pydantic_core import ValidationError +import pytest + from aiida.storage.sqlite_zip.backend import SqliteZipBackend from aiida.storage.sqlite_zip.migrator import validate_storage @@ -45,3 +50,16 @@ def test_initialise_reset_false(tmp_path, aiida_caplog): assert filepath_archive.exists() validate_storage(filepath_archive) assert any('Migrating existing SqliteZipBackend' in record.message for record in aiida_caplog.records) + + +@pytest.mark.usefixtures('chdir_tmp_path') +def test_configuration(): + """Test :class:`aiida.storage.sqlite_zip.backend.SqliteZipBackend.Configuration`.""" + with pytest.raises(ValidationError, match=r'.*The archive `non-existent` does not exist.*'): + SqliteZipBackend.Configuration(filepath='non-existent') + + filepath = pathlib.Path.cwd() / 'archive.aiida' + filepath.touch() + + configuration = SqliteZipBackend.Configuration(filepath=filepath.name) + assert pathlib.Path(configuration.filepath).is_absolute()