Skip to content

Commit

Permalink
Create parent directories if necessary (tox-dev#254)
Browse files Browse the repository at this point in the history
Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
  • Loading branch information
2 people authored and TheMatt2 committed Jul 29, 2023
1 parent 36ca4d9 commit 72980ac
Show file tree
Hide file tree
Showing 5 changed files with 21 additions and 15 deletions.
3 changes: 2 additions & 1 deletion src/filelock/_soft.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,15 @@
from pathlib import Path

from ._api import BaseFileLock
from ._util import raise_on_not_writable_file
from ._util import ensure_directory_exists, raise_on_not_writable_file


class SoftFileLock(BaseFileLock):
"""Simply watches the existence of the lock file."""

def _acquire(self) -> None:
raise_on_not_writable_file(self.lock_file)
ensure_directory_exists(self.lock_file)
# first check for exists and read-only mode as the open will mask this case as EEXIST
flags = (
os.O_WRONLY # open for writing only
Expand Down
2 changes: 2 additions & 0 deletions src/filelock/_unix.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from typing import cast

from ._api import BaseFileLock
from ._util import ensure_directory_exists

#: a flag to indicate if the fcntl API is available
has_fcntl = False
Expand All @@ -33,6 +34,7 @@ class UnixFileLock(BaseFileLock):
"""Uses the :func:`fcntl.flock` to hard lock the lock file on unix systems."""

def _acquire(self) -> None:
ensure_directory_exists(self.lock_file)
open_flags = os.O_RDWR | os.O_CREAT | os.O_TRUNC
fd = os.open(self.lock_file, open_flags, self._context.mode)
with suppress(PermissionError): # This locked is not owned by this UID
Expand Down
10 changes: 10 additions & 0 deletions src/filelock/_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import stat
import sys
from errno import EACCES, EISDIR
from pathlib import Path


def raise_on_not_writable_file(filename: str) -> None:
Expand Down Expand Up @@ -32,6 +33,15 @@ def raise_on_not_writable_file(filename: str) -> None:
raise IsADirectoryError(EISDIR, "Is a directory", filename)


def ensure_directory_exists(filename: Path | str) -> None:
"""
Ensure the directory containing the file exists (create it if necessary)
:param filename: file.
"""
Path(filename).parent.mkdir(parents=True, exist_ok=True)


__all__ = [
"raise_on_not_writable_file",
"ensure_directory_exists",
]
3 changes: 2 additions & 1 deletion src/filelock/_windows.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from typing import cast

from ._api import BaseFileLock
from ._util import raise_on_not_writable_file
from ._util import ensure_directory_exists, raise_on_not_writable_file

if sys.platform == "win32": # pragma: win32 cover
import msvcrt
Expand All @@ -18,6 +18,7 @@ class WindowsFileLock(BaseFileLock):

def _acquire(self) -> None:
raise_on_not_writable_file(self.lock_file)
ensure_directory_exists(self.lock_file)
flags = (
os.O_RDWR # open for read and write
| os.O_CREAT # create file if not exists
Expand Down
18 changes: 5 additions & 13 deletions tests/test_filelock.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,27 +23,20 @@
from pytest_mock import MockerFixture


@pytest.mark.parametrize(
("lock_type", "path_type"),
[
(FileLock, str),
(FileLock, PurePath),
(FileLock, Path),
(SoftFileLock, str),
(SoftFileLock, PurePath),
(SoftFileLock, Path),
],
)
@pytest.mark.parametrize("lock_type", [FileLock, SoftFileLock])
@pytest.mark.parametrize("path_type", [str, PurePath, Path])
@pytest.mark.parametrize("filename", ["a", "new/b", "new2/new3/c"])
def test_simple(
lock_type: type[BaseFileLock],
path_type: type[str] | type[Path],
filename: str,
tmp_path: Path,
caplog: pytest.LogCaptureFixture,
) -> None:
caplog.set_level(logging.DEBUG)

# test lock creation by passing a `str`
lock_path = tmp_path / "a"
lock_path = tmp_path / filename
lock = lock_type(path_type(lock_path))
with lock as locked:
assert lock.is_locked
Expand Down Expand Up @@ -113,7 +106,6 @@ def test_ro_file(lock_type: type[BaseFileLock], tmp_file_ro: Path) -> None:
@pytest.mark.parametrize(
("expected_error", "match", "bad_lock_file"),
[
pytest.param(FileNotFoundError, "No such file or directory:", "a/b", id="non_existent_directory"),
pytest.param(FileNotFoundError, "No such file or directory:", "", id="blank_filename"),
pytest.param(ValueError, "embedded null (byte|character)", "\0", id="null_byte"),
# Should be PermissionError on Windows
Expand Down

0 comments on commit 72980ac

Please sign in to comment.