Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Breaking Changes

- Renamed `filesystem.validate_zimfile_creatable` to `filesystem.file_creatable` to reflect general applicability to check file creation beyond ZIM files #200
- Remove any "ZIM" reference in exceptions while working with files #200

### Added

- Add `filesystem.validate_folder_writable` to check if a folder can be written to #200

## [4.0.0] - 2024-08-05

### Added
Expand Down Expand Up @@ -38,6 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **BREAKING** Rename `i18.NotFound` to `i18n.NotFoundError`

### Removed

- **BREAKING** Remove translation features in `i18n`: `Locale` class + `_` and `setlocale` functions #134

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion src/zimscraperlib/__about__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "4.0.1-dev0"
__version__ = "5.0.0-dev0"
71 changes: 37 additions & 34 deletions src/zimscraperlib/zim/filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,33 +219,33 @@ def make_zim_file(
zim_file.finish()


class IncorrectZIMPathError(Exception):
"""A generic exception for any problem encountered in validate_zimfile_creatable"""
class IncorrectPathError(Exception):
"""A generic exception for any problem encountered while working with filepaths"""

pass


class MissingZIMFolderError(IncorrectZIMPathError):
"""Exception raised in validate_zimfile_creatable when folder does not exists"""
class MissingFolderError(IncorrectPathError):
"""Exception raised when folder does not exist"""

pass


class NotADirectoryZIMFolderError(IncorrectZIMPathError):
"""Exception raised in validate_zimfile_creatable when folder is not a directory"""
class NotADirectoryFolderError(IncorrectPathError):
"""Exception raised when folder is not a directory"""

pass


class NotWritableZIMFolderError(IncorrectZIMPathError):
"""Exception raised in validate_zimfile_creatable when folder is not writable"""
class NotWritableFolderError(IncorrectPathError):
"""Exception raised when folder is not writable"""

pass


class IncorrectZIMFilenameError(IncorrectZIMPathError):
class IncorrectFilenameError(IncorrectPathError):
"""
Exception raised in validate_zimfile_creatable when filename is not creatable
Exception raised when filename is not creatable

This usually occurs when bad characters are present in filename (typically
characters not supported on current filesystem).
Expand All @@ -254,32 +254,24 @@ class IncorrectZIMFilenameError(IncorrectZIMPathError):
pass


def validate_zimfile_creatable(folder: str | pathlib.Path, filename: str):
"""Validate that a ZIM can be created in given folder with given filename
def validate_folder_writable(folder: pathlib.Path):
"""Validate that a file can be created in a given folder.

Any problem encountered raises an exception inheriting from IncorrectZIMPathError
Any problem encountered raises an exception inheriting from IncorrectPathError

Checks that:
- folder passed exists (or raise MissingZIMFolderError exception)
- folder passed is a directory (or raise NotADirectoryZIMFolderError exception)
- folder passed exists (or raise MissingFolderError exception)
- folder passed is a directory (or raise NotADirectoryFolderError exception)
- folder is writable, i.e. it is possible to create a file in folder (or raise
NotWritableZIMFolderError exception with inner exception details)
- filename is creatable, i.e. there is no bad characters in filename (or raise
IncorrectZIMFilenameError exception with inner exception details)
NotWritableFolderError exception with inner exception details)
"""
folder = pathlib.Path(folder)

# ensure folder exists
if not folder.exists():
raise MissingZIMFolderError(
f"Folder to create the ZIM does not exist: {folder}"
)
raise MissingFolderError(f"Folder does not exist: {folder}")

# ensure folder is a directory
if not folder.is_dir():
raise NotADirectoryZIMFolderError(
f"Folder to create the ZIM is not a directory: {folder}"
)
raise NotADirectoryFolderError(f"Folder is not a directory: {folder}")

logger.debug(f"Attempting to confirm output is writable in directory {folder}")

Expand All @@ -288,17 +280,28 @@ def validate_zimfile_creatable(folder: str | pathlib.Path, filename: str):
with tempfile.NamedTemporaryFile(dir=folder, delete=True) as fh:
logger.debug(f"Output is writable. Temporary file used for test: {fh.name}")
except Exception as exc:
raise NotWritableZIMFolderError(
f"Folder to create the ZIM is not writable: {folder}"
) from exc
raise NotWritableFolderError(f"Folder is not writable: {folder}") from exc


def validate_file_creatable(folder: str | pathlib.Path, filename: str):
"""Validate that a file can be created in given folder with given filename

Any problem encountered raises an exception inheriting from IncorrectPathError

Checks that:
- folder is writable (or raise exception from `validate_folder_writable`)
- file can be created (or raise IncorrectFilenameError exception with
inner exception details)
"""
folder = pathlib.Path(folder)

validate_folder_writable(folder)

# ensure ZIM file is creatable with the given name
# ensure file is creatable with the given name
fpath = folder / filename
try:
logger.debug(f"Confirming ZIM file can be created at {fpath}")
logger.debug(f"Confirming file can be created at {fpath}")
fpath.touch()
fpath.unlink()
except Exception as exc:
raise IncorrectZIMFilenameError(
f"ZIM filename is not creatable: {fpath}"
) from exc
raise IncorrectFilenameError(f"File is not creatable: {fpath}") from exc
47 changes: 26 additions & 21 deletions tests/zim/test_fs.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@
from zimscraperlib.zim.archive import Archive
from zimscraperlib.zim.filesystem import (
FileItem,
IncorrectZIMFilenameError,
MissingZIMFolderError,
NotADirectoryZIMFolderError,
NotWritableZIMFolderError,
IncorrectFilenameError,
MissingFolderError,
NotADirectoryFolderError,
NotWritableFolderError,
make_zim_file,
validate_zimfile_creatable,
validate_file_creatable,
validate_folder_writable,
)


Expand Down Expand Up @@ -171,32 +172,36 @@ def valid_zim_filename():
return "myfile.zim"


def test_validate_zimfile_creatable_ok(tmp_path, valid_zim_filename):
def test_validate_folder_writable_not_exists(tmp_path):

validate_zimfile_creatable(tmp_path, valid_zim_filename)
with pytest.raises(MissingFolderError):
validate_folder_writable(tmp_path / "foo")


def test_validate_zimfile_creatable_folder_not_exists(tmp_path, valid_zim_filename):
def test_validate_folder_writable_not_dir(tmp_path):

with pytest.raises(MissingZIMFolderError):
validate_zimfile_creatable(tmp_path / "foo", valid_zim_filename)
with pytest.raises(NotADirectoryFolderError):
(tmp_path / "foo.txt").touch()
validate_folder_writable(tmp_path / "foo.txt")


def test_validate_zimfile_creatable_bad_folder(tmp_path, valid_zim_filename):
def test_validate_folder_writable_not_writable(tmp_path):

with pytest.raises(NotADirectoryZIMFolderError):
(tmp_path / "foo.txt").touch()
validate_zimfile_creatable(tmp_path / "foo.txt", valid_zim_filename)
with pytest.raises(NotWritableFolderError):
(tmp_path / "foo").mkdir(mode=111)
validate_folder_writable(tmp_path / "foo")


def test_validate_zimfile_creatable_folder_not_writable(tmp_path, valid_zim_filename):
def test_validate_folder_writable_ok(tmp_path):
validate_folder_writable(tmp_path)

with pytest.raises(NotWritableZIMFolderError):
(tmp_path / "foo").mkdir(mode=111)
validate_zimfile_creatable(tmp_path / "foo", valid_zim_filename)

def test_validate_file_creatable_ok(tmp_path, valid_zim_filename):

validate_file_creatable(tmp_path, valid_zim_filename)


def test_validate_zimfile_creatable_bad_name(tmp_path):
def test_validate_file_creatable_bad_name(tmp_path):

with pytest.raises(IncorrectZIMFilenameError):
validate_zimfile_creatable(tmp_path, "t\0t\0.zim")
with pytest.raises(IncorrectFilenameError):
validate_file_creatable(tmp_path, "t\0t\0.zim")