Browse Source

Improve FileMetadata & tests.

dev
Draklaw 6 months ago
parent
commit
2b961baa5b
  1. 236
      src/bsv/vfs.py
  2. 170
      tests/test_bsv/test_vfs.py

236
src/bsv/vfs.py

@ -79,6 +79,119 @@ DEFAULT_DIR_PERMS = Permissions(0o770)
DEFAULT_FILE_PERMS = Permissions(0o640)
FileType = Literal["dir", "file", "symlink", "other"]
_IFMT_MAP: dict[int, FileType] = {
S_IFDIR: "dir",
S_IFREG: "file",
S_IFLNK: "symlink",
}
@total_ordering
class FileMetadata:
"""Metadata associated with vfs files: file type, permissions, etc."""
path: PurePosixPath
type: FileType
permissions: Permissions
modification_time: datetime
byte_size: int
_stat: stat_result
def __init__(
self,
path: PurePosixPath,
*,
type: FileType,
permissions: Permissions,
modification_time: datetime,
byte_size: int,
):
"""Create a `FileMetadata`."""
self.path = path
self.type = type
self.permissions = permissions
self.modification_time = modification_time
self.byte_size = byte_size
@classmethod
def from_stat(
cls,
path: PurePosixPath,
stat: stat_result,
) -> Self:
"""Create a `FileMetadata` from a `stat_result`."""
return cls(
path,
type=_IFMT_MAP.get(S_IFMT(stat.st_mode), "other"),
permissions=Permissions(S_IMODE(stat.st_mode)),
modification_time=datetime.fromtimestamp(stat.st_mtime, UTC),
byte_size=stat.st_size,
)
@property
def unix_mode(self) -> str:
"""Return unix-like mode in the form '-rwxrwxrwx'."""
return UNIX_MODE_FILE_TYPE[self.type] + str(self.permissions)
@property
def is_hidden(self) -> bool:
"""Return true if the file starts with a '.'."""
return self.path.name.startswith(".")
@property
def is_file(self) -> bool:
"""Test if this is a file."""
return self.type == "file"
@property
def is_dir(self) -> bool:
"""Test if this is a directory."""
return self.type == "dir"
@property
def is_symlink(self) -> bool:
"""Test if this is a symbolic link."""
return self.type == "symlink"
@property
def is_other(self) -> bool:
"""Test if this is a symbolic link."""
return self.type == "other"
def _as_tuple(
self,
) -> tuple[PurePosixPath, FileType, Permissions, datetime, int]:
return (
self.path,
self.type,
self.permissions,
self.modification_time,
self.byte_size,
)
def __eq__(self, rhs: Any) -> bool:
"""Test if two `Metadata` are the same."""
return (
self._as_tuple() == rhs._as_tuple()
if isinstance(rhs, FileMetadata)
else NotImplemented
)
def __lt__(self, rhs: Any) -> bool:
"""Compare `rhs.path` with `self.path`."""
return self.path < rhs.path if isinstance(rhs, FileMetadata) else NotImplemented
UNIX_MODE_FILE_TYPE = {
"dir": "d",
"file": "-",
"other": "o",
"link": "l",
}
class VirtualFileSystem:
"""Represent a file system, with common file system operations."""
@ -127,7 +240,7 @@ class VirtualFileSystem:
except OSError as err:
msg = f"failed to read '{path}' metadata"
raise FsError(msg) from err
return FileMetadata.from_stat(self, path, stat)
return FileMetadata.from_stat(path, stat)
def iter_dir(self, path: AnyBsvPath) -> Iterator[FileMetadata]:
"""Return the metadata of all items in the directory `path`."""
@ -136,7 +249,7 @@ class VirtualFileSystem:
try:
for entry in os.scandir(real_path):
yield FileMetadata.from_stat(
self, path / entry.name, entry.stat(follow_symlinks=False)
path / entry.name, entry.stat(follow_symlinks=False)
)
except OSError as err:
msg = f"failed to read directory {path}"
@ -233,122 +346,3 @@ class VirtualFileSystem:
def _real_path(self, path: PurePosixPath) -> Path:
return self.path / path.relative_to("/")
FileType = Literal["dir", "file", "symlink", "other"]
_IFMT_MAP: dict[int, FileType] = {
S_IFDIR: "dir",
S_IFREG: "file",
S_IFLNK: "symlink",
}
@total_ordering
class FileMetadata:
"""Metadata associated with vfs files: file type, permissions, etc."""
vfs: VirtualFileSystem
path: PurePosixPath
type: FileType
permissions: Permissions
modification_time: datetime
byte_size: int
_stat: stat_result
def __init__(
self,
vfs: VirtualFileSystem,
path: PurePosixPath,
*,
type: FileType,
permissions: Permissions,
modification_time: datetime,
byte_size: int,
):
"""Create a `FileMetadata`."""
self.vfs = vfs
self.path = path
self.type = type
self.permissions = permissions
self.modification_time = modification_time
self.byte_size = byte_size
@classmethod
def from_stat(
cls,
vfs: VirtualFileSystem,
path: PurePosixPath,
stat: stat_result,
) -> Self:
"""Create a `FileMetadata` from a `stat_result`."""
return cls(
vfs,
path,
type=_IFMT_MAP.get(S_IFMT(stat.st_mode), "other"),
permissions=Permissions(S_IMODE(stat.st_mode)),
modification_time=datetime.fromtimestamp(stat.st_mtime, UTC),
byte_size=stat.st_size,
)
@property
def unix_mode(self) -> str:
"""Return unix-like mode in the form '-rwxrwxrwx'."""
return UNIX_MODE_FILE_TYPE[self.type] + str(self.permissions)
@property
def is_hidden_files(self) -> bool:
"""Return true if the file starts with a '.'."""
return self.path.name.startswith(".")
@property
def is_file(self) -> bool:
"""Test if this is a file."""
return self.type == "file"
@property
def is_dir(self) -> bool:
"""Test if this is a directory."""
return self.type == "dir"
@property
def is_symlink(self) -> bool:
"""Test if this is a symbolic link."""
return self.type == "symlink"
@property
def is_other(self) -> bool:
"""Test if this is a symbolic link."""
return self.type == "other"
def _as_tuple(
self,
) -> tuple[VirtualFileSystem, PurePosixPath, FileType, Permissions, datetime, int]:
return (
self.vfs,
self.path,
self.type,
self.permissions,
self.modification_time,
self.byte_size,
)
def __eq__(self, rhs: Any) -> bool:
"""Test if two `Metadata` are the same."""
return (
self._as_tuple() == rhs._as_tuple()
if isinstance(rhs, FileMetadata)
else NotImplemented
)
def __lt__(self, rhs: Any) -> bool:
"""Compare `rhs.path` with `self.path`."""
return self.path < rhs.path if isinstance(rhs, FileMetadata) else NotImplemented
UNIX_MODE_FILE_TYPE = {
"dir": "d",
"file": "-",
"other": "o",
"link": "l",
}

170
tests/test_bsv/test_vfs.py

@ -23,7 +23,7 @@ from pathlib import Path, PurePosixPath
import pytest
from bsv.vfs import FsError, Permissions, VirtualFileSystem
from bsv.vfs import FileMetadata, FsError, Permissions, VirtualFileSystem
@pytest.fixture
@ -53,6 +53,158 @@ def test_permissions():
assert str(perm1) == "rwxr-x-w-"
########################################################################################
# FileMetadata
def test_file_metadata():
path = PurePosixPath("/some_dir/some_file")
permissions = Permissions(0o1234)
mod_time = datetime(2025, 7, 12, 12, 34, 56, tzinfo=UTC)
file_md = FileMetadata(
path,
type="file",
permissions=permissions,
modification_time=mod_time,
byte_size=123,
)
assert file_md.path == path
assert file_md.type == "file"
assert file_md.permissions == permissions
assert file_md.modification_time == mod_time
assert file_md.byte_size == 123
assert file_md.unix_mode == "--w--wxr-T"
assert not file_md.is_hidden
assert file_md.is_file
assert not file_md.is_dir
assert not file_md.is_symlink
assert not file_md.is_other
dir_md = FileMetadata(
path,
type="dir",
permissions=permissions,
modification_time=mod_time,
byte_size=123,
)
assert dir_md.type == "dir"
assert not dir_md.is_file
assert dir_md.is_dir
assert not dir_md.is_symlink
assert not dir_md.is_other
symlink_md = FileMetadata(
path,
type="symlink",
permissions=permissions,
modification_time=mod_time,
byte_size=123,
)
assert symlink_md.type == "symlink"
assert not symlink_md.is_file
assert not symlink_md.is_dir
assert symlink_md.is_symlink
assert not symlink_md.is_other
other_md = FileMetadata(
path,
type="other",
permissions=permissions,
modification_time=mod_time,
byte_size=123,
)
assert other_md.type == "other"
assert not other_md.is_file
assert not other_md.is_dir
assert not other_md.is_symlink
assert other_md.is_other
assert FileMetadata(
PurePosixPath("/some_dir/.some_file"),
type="file",
permissions=permissions,
modification_time=mod_time,
byte_size=123,
).is_hidden
def test_file_metadata_eq():
path = PurePosixPath("/some_dir/some_file")
permissions = Permissions(0o1234)
mod_time = datetime(2025, 7, 12, 12, 34, 56, tzinfo=UTC)
md = FileMetadata(
path,
type="file",
permissions=permissions,
modification_time=mod_time,
byte_size=123,
)
assert (
FileMetadata(
path,
type="file",
permissions=permissions,
modification_time=mod_time,
byte_size=123,
)
== md
)
assert (
FileMetadata(
PurePosixPath("/some_dir/some_other_file"),
type="file",
permissions=permissions,
modification_time=mod_time,
byte_size=123,
)
!= md
)
assert (
FileMetadata(
path,
type="dir",
permissions=permissions,
modification_time=mod_time,
byte_size=123,
)
!= md
)
assert (
FileMetadata(
path,
type="file",
permissions=Permissions(0o0752),
modification_time=mod_time,
byte_size=123,
)
!= md
)
assert (
FileMetadata(
path,
type="file",
permissions=permissions,
modification_time=datetime(2025, 1, 2, 3, 4, 5),
byte_size=123,
)
!= md
)
assert (
FileMetadata(
path,
type="file",
permissions=permissions,
modification_time=mod_time,
byte_size=124,
)
!= md
)
########################################################################################
# mkdir
@ -175,13 +327,9 @@ def test_metadata(fs: VirtualFileSystem):
assert md.path == PurePosixPath("/test_file")
assert md.permissions == file_permissions
assert md.type == "file"
assert md.is_file
assert not md.is_dir
assert not md.is_symlink
assert not md.is_other
assert md.modification_time == file_time
assert md.byte_size == len(file_content)
assert not md.is_hidden_files
assert not md.is_hidden
assert fs.metadata("/test_file") == md
assert fs.is_file("/test_file")
assert not fs.is_dir("/test_file")
@ -194,11 +342,7 @@ def test_metadata(fs: VirtualFileSystem):
fs.mkdir("/.test_dir")
md = fs.metadata("/.test_dir")
assert md.type == "dir"
assert not md.is_file
assert md.is_dir
assert not md.is_symlink
assert not md.is_other
assert fs.metadata("/.test_dir").is_hidden_files
assert fs.metadata("/.test_dir").is_hidden
assert not fs.is_file("/.test_dir")
assert fs.is_dir("/.test_dir")
assert not fs.is_symlink("/.test_dir")
@ -207,10 +351,6 @@ def test_metadata(fs: VirtualFileSystem):
fs.make_link("/test_link", "/link_target")
md = fs.metadata("/test_link")
assert md.type == "symlink"
assert not md.is_file
assert not md.is_dir
assert md.is_symlink
assert not md.is_other
assert not fs.is_file("/test_link")
assert not fs.is_dir("/test_link")
assert fs.is_symlink("/test_link")

Loading…
Cancel
Save