From c9bac139d95030724fae32bb9cfbfc438adef71e Mon Sep 17 00:00:00 2001 From: Schamper <1254028+Schamper@users.noreply.github.com> Date: Thu, 23 Jan 2025 11:06:05 +0100 Subject: [PATCH] Change linter to Ruff --- dissect/squashfs/__init__.py | 4 +-- dissect/squashfs/c_squashfs.py | 2 ++ dissect/squashfs/compression.py | 11 +++--- dissect/squashfs/squashfs.py | 59 ++++++++++++++------------------- pyproject.toml | 53 ++++++++++++++++++++++++++--- tests/conftest.py | 38 ++++++++++++--------- tests/test_exceptions.py | 4 ++- tests/test_squashfs.py | 10 +++--- tox.ini | 21 +++--------- 9 files changed, 116 insertions(+), 86 deletions(-) diff --git a/dissect/squashfs/__init__.py b/dissect/squashfs/__init__.py index 75bfbd6..eec91fa 100644 --- a/dissect/squashfs/__init__.py +++ b/dissect/squashfs/__init__.py @@ -10,10 +10,10 @@ __all__ = [ "Error", "FileNotFoundError", + "FileStream", + "INode", "NotADirectoryError", "NotAFileError", "NotASymlinkError", - "FileStream", - "INode", "SquashFS", ] diff --git a/dissect/squashfs/c_squashfs.py b/dissect/squashfs/c_squashfs.py index 6a4a460..b2d576c 100644 --- a/dissect/squashfs/c_squashfs.py +++ b/dissect/squashfs/c_squashfs.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import stat from dissect.cstruct import cstruct diff --git a/dissect/squashfs/compression.py b/dissect/squashfs/compression.py index 762f113..25f7c35 100644 --- a/dissect/squashfs/compression.py +++ b/dissect/squashfs/compression.py @@ -1,12 +1,11 @@ from __future__ import annotations import importlib -from typing import Optional from dissect.squashfs.c_squashfs import c_squashfs -def initialize(id: int, options: Optional[bytes]) -> Compression: +def initialize(id: int, options: bytes | None) -> Compression: # Options have no effect on decompression, so ignore for now modules = { c_squashfs.ZLIB_COMPRESSION: (NativeZlib,), @@ -21,10 +20,10 @@ def initialize(id: int, options: Optional[bytes]) -> Compression: for mod in modules[id]: try: return mod() - except ModuleNotFoundError: + except ModuleNotFoundError: # noqa: PERF203 pass else: - raise ImportError(f"No modules available ({modules[id]})") + raise ImportError(f"No modules available ({modules[id]})") # noqa: TRY301 except ImportError: raise ValueError(f"Compression ID {id} requested but module ({modules[id]}) is not available") except KeyError: @@ -38,10 +37,10 @@ def __init__(self): self._module = importlib.import_module(self.module) def compress(self, data: bytes) -> bytes: - raise NotImplementedError() + raise NotImplementedError def decompress(self, data: bytes, expected: int) -> bytes: - raise NotImplementedError() + raise NotImplementedError class NativeZlib(Compression): diff --git a/dissect/squashfs/squashfs.py b/dissect/squashfs/squashfs.py index 1653a45..550699e 100644 --- a/dissect/squashfs/squashfs.py +++ b/dissect/squashfs/squashfs.py @@ -7,9 +7,8 @@ import stat import struct from bisect import bisect_right -from datetime import datetime from functools import cache, cached_property, lru_cache -from typing import BinaryIO, Iterator, Optional, Union +from typing import TYPE_CHECKING, BinaryIO from dissect.util import ts from dissect.util.stream import RunlistStream @@ -23,6 +22,10 @@ NotASymlinkError, ) +if TYPE_CHECKING: + from collections.abc import Iterator + from datetime import datetime + class SquashFS: def __init__(self, fh: BinaryIO): @@ -68,15 +71,15 @@ def inode( self, block: int, offset: int, - name: Optional[str] = None, - type: Optional[int] = None, - inode_number: Optional[int] = None, - parent: Optional[INode] = None, + name: str | None = None, + type: int | None = None, + inode_number: int | None = None, + parent: INode | None = None, ) -> INode: # squashfs inode numbers consist of a block number and offset in that block return INode(self, block, offset, name, type, inode_number, parent) - def get(self, path: Union[str, int], node: Optional[INode] = None) -> INode: + def get(self, path: str | int, node: INode | None = None) -> INode: if isinstance(path, int): return self.inode(path >> 16, path & 0xFFFF) @@ -135,7 +138,7 @@ def _read_metadata(self, block: int, offset: int, length: int) -> tuple[int, int return block, offset, b"".join(result) - def _read_block(self, block: int, length: Optional[int] = None) -> tuple[int, bytes]: + def _read_block(self, block: int, length: int | None = None) -> tuple[int, bytes]: if length is not None: # Data block compressed = length & c_squashfs.SQUASHFS_COMPRESSED_BIT_BLOCK == 0 @@ -177,13 +180,12 @@ def _lookup_inode(self, inode_number: int) -> INode: _, _, data = self._read_metadata(self.lookup_table[block], offset, 8) return self.get(struct.unpack(" bytes: + def _lookup_fragment(self, fragment: int) -> c_squashfs.squashfs_fragment_entry: fragment_offset = fragment * len(c_squashfs.squashfs_fragment_entry) block, offset = divmod(fragment_offset, c_squashfs.SQUASHFS_METADATA_SIZE) _, _, data = self._read_metadata(self.fragment_table[block], offset, len(c_squashfs.squashfs_fragment_entry)) - entry = c_squashfs.squashfs_fragment_entry(data) - return entry + return c_squashfs.squashfs_fragment_entry(data) def iter_inodes(self) -> Iterator[INode]: for inum in range(1, self.sb.inodes + 1): @@ -196,10 +198,10 @@ def __init__( fs: SquashFS, block: int, offset: int, - name: Optional[str] = None, - type: Optional[int] = None, - inode_number: Optional[int] = None, - parent: Optional[INode] = None, + name: str | None = None, + type: int | None = None, + inode_number: int | None = None, + parent: INode | None = None, ): self.fs = fs self.block = block @@ -209,6 +211,8 @@ def __init__( self._inode_number = inode_number self.parent = parent + self.listdir = cache(self.listdir) + def __repr__(self) -> str: return f"" @@ -220,15 +224,9 @@ def _metadata( | c_squashfs.squashfs_reg_inode_header | c_squashfs.squashfs_symlink_inode_header | c_squashfs.squashfs_dev_inode_header - | c_squashfs.squashfs_dev_inode_header - | c_squashfs.squashfs_base_inode_header - | c_squashfs.squashfs_base_inode_header | c_squashfs.squashfs_ldir_inode_header | c_squashfs.squashfs_lreg_inode_header - | c_squashfs.squashfs_symlink_inode_header - | c_squashfs.squashfs_ldev_inode_header | c_squashfs.squashfs_ldev_inode_header - | c_squashfs.squashfs_lipc_inode_header | c_squashfs.squashfs_lipc_inode_header, int, int, @@ -262,16 +260,10 @@ def header( | c_squashfs.squashfs_reg_inode_header | c_squashfs.squashfs_symlink_inode_header | c_squashfs.squashfs_dev_inode_header - | c_squashfs.squashfs_dev_inode_header - | c_squashfs.squashfs_base_inode_header - | c_squashfs.squashfs_base_inode_header | c_squashfs.squashfs_ldir_inode_header | c_squashfs.squashfs_lreg_inode_header - | c_squashfs.squashfs_symlink_inode_header - | c_squashfs.squashfs_ldev_inode_header | c_squashfs.squashfs_ldev_inode_header | c_squashfs.squashfs_lipc_inode_header - | c_squashfs.squashfs_lipc_inode_header ): header, _, _ = self._metadata() return header @@ -315,11 +307,12 @@ def mtime(self) -> datetime: return ts.from_unix(self.header.mtime) @property - def size(self) -> Optional[int]: + def size(self) -> int | None: if self.is_dir() or self.is_file(): return self.header.file_size - elif self.is_symlink(): + if self.is_symlink(): return self.header.symlink_size + return None def is_dir(self) -> bool: return self.type == stat.S_IFDIR @@ -363,13 +356,9 @@ def link(self) -> str: @cached_property def link_inode(self) -> INode: link = self.link - if link.startswith("/"): - relnode = None - else: - relnode = self.parent + relnode = None if link.startswith("/") else self.parent return self.fs.get(self.link, relnode) - @cache def listdir(self) -> dict[str, INode]: return {inode.name: inode for inode in self.iterdir()} @@ -407,7 +396,7 @@ def iterdir(self) -> Iterator[INode]: ) @cached_property - def block_list(self) -> list[tuple[Optional[int], int]]: + def block_list(self) -> list[tuple[int | None, int]]: fragment = self.header.fragment file_size = self.header.file_size if fragment == c_squashfs.SQUASHFS_INVALID_FRAG: diff --git a/pyproject.toml b/pyproject.toml index f40d8b3..cf593c0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -53,13 +53,56 @@ dev = [ "dissect.util>=3.0.dev,<4.0.dev", ] -[tool.black] +[tool.ruff] line-length = 120 +required-version = ">=0.9.0" -[tool.isort] -profile = "black" -known_first_party = ["dissect.squashfs"] -known_third_party = ["dissect"] +[tool.ruff.format] +docstring-code-format = true + +[tool.ruff.lint] +select = [ + "F", + "E", + "W", + "I", + "UP", + "YTT", + "ANN", + "B", + "C4", + "DTZ", + "T10", + "FA", + "ISC", + "G", + "INP", + "PIE", + "PYI", + "PT", + "Q", + "RSE", + "RET", + "SLOT", + "SIM", + "TID", + "TCH", + "PTH", + "PLC", + "TRY", + "FLY", + "PERF", + "FURB", + "RUF", +] +ignore = ["E203", "B904", "UP024", "ANN002", "ANN003", "ANN204", "ANN401", "SIM105", "TRY003"] + +[tool.ruff.lint.per-file-ignores] +"tests/docs/**" = ["INP001"] + +[tool.ruff.lint.isort] +known-first-party = ["dissect.squashfs"] +known-third-party = ["dissect"] [tool.setuptools] license-files = ["LICENSE", "COPYRIGHT"] diff --git a/tests/conftest.py b/tests/conftest.py index 8c96964..c179766 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,53 +1,59 @@ +from __future__ import annotations + import gzip -import os +from pathlib import Path +from typing import TYPE_CHECKING, BinaryIO import pytest +if TYPE_CHECKING: + from collections.abc import Iterator + -def absolute_path(filename): - return os.path.join(os.path.dirname(__file__), filename) +def absolute_path(filename: str) -> Path: + return Path(__file__).parent / filename -def open_file(name, mode="rb"): - with open(absolute_path(name), mode) as f: - yield f +def open_file(name: str, mode: str = "rb") -> Iterator[BinaryIO]: + with absolute_path(name).open(mode) as fh: + yield fh -def open_file_gz(name, mode="rb"): - with gzip.GzipFile(absolute_path(name), mode) as f: - yield f +def open_file_gz(name: str, mode: str = "rb") -> Iterator[BinaryIO]: + with gzip.GzipFile(absolute_path(name), mode) as fh: + yield fh @pytest.fixture -def gzip_sqfs(): +def gzip_sqfs() -> Iterator[BinaryIO]: yield from open_file("data/gzip.sqfs") @pytest.fixture -def gzip_opts_sqfs(): +def gzip_opts_sqfs() -> Iterator[BinaryIO]: yield from open_file("data/gzip-opts.sqfs") @pytest.fixture -def lz4_sqfs(): +def lz4_sqfs() -> Iterator[BinaryIO]: yield from open_file("data/lz4.sqfs") @pytest.fixture -def lzma_sqfs(): +def lzma_sqfs() -> Iterator[BinaryIO]: yield from open_file("data/lzma.sqfs") @pytest.fixture -def lzo_sqfs(): +def lzo_sqfs() -> Iterator[BinaryIO]: yield from open_file("data/lzo.sqfs") @pytest.fixture -def xz_sqfs(): +def xz_sqfs() -> Iterator[BinaryIO]: yield from open_file("data/xz.sqfs") @pytest.fixture -def zstd_sqfs(): +def zstd_sqfs() -> Iterator[BinaryIO]: yield from open_file("data/zstd.sqfs") diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 66026ab..b42f311 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -1,10 +1,12 @@ +from __future__ import annotations + import pytest from dissect.squashfs import exceptions @pytest.mark.parametrize( - "exc, std", + ("exc", "std"), [ (exceptions.FileNotFoundError, FileNotFoundError), (exceptions.IsADirectoryError, IsADirectoryError), diff --git a/tests/test_squashfs.py b/tests/test_squashfs.py index 64afdba..161f6ab 100644 --- a/tests/test_squashfs.py +++ b/tests/test_squashfs.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from dissect.squashfs.c_squashfs import c_squashfs @@ -9,7 +11,7 @@ from dissect.squashfs.squashfs import SquashFS -def _verify_filesystem(sqfs): +def _verify_filesystem(sqfs: SquashFS) -> None: assert sqfs.root.is_dir() assert sorted(sqfs.root.listdir().keys()) == [ "dir", @@ -52,11 +54,11 @@ def _verify_filesystem(sqfs): sqfs.get("small-file").listdir() with pytest.raises(NotASymlinkError): - sqfs.get("large-file").link + assert sqfs.get("large-file").link @pytest.mark.parametrize( - "sqfs,compression_id", + ("sqfs", "compression_id"), [ ("gzip_sqfs", c_squashfs.ZLIB_COMPRESSION), ("gzip_opts_sqfs", c_squashfs.ZLIB_COMPRESSION), @@ -69,7 +71,7 @@ def _verify_filesystem(sqfs): ("zstd_sqfs", c_squashfs.ZSTD_COMPRESSION), ], ) -def test_squashfs(sqfs, compression_id, request): +def test_squashfs(sqfs: str, compression_id: int, request: pytest.FixtureRequest) -> None: sqfs = SquashFS(request.getfixturevalue(sqfs)) assert sqfs.sb.compression == compression_id _verify_filesystem(sqfs) diff --git a/tox.ini b/tox.ini index bfcf133..17e3629 100644 --- a/tox.ini +++ b/tox.ini @@ -32,32 +32,19 @@ commands = [testenv:fix] package = skip deps = - black==23.1.0 - isort==5.11.4 + ruff==0.9.2 commands = - black dissect tests - isort dissect tests + ruff format dissect tests [testenv:lint] package = skip deps = - black==23.1.0 - flake8 - flake8-black - flake8-isort - isort==5.11.4 + ruff==0.9.2 vermin commands = - flake8 dissect tests + ruff check dissect tests vermin -t=3.9- --no-tips --lint dissect tests -[flake8] -max-line-length = 120 -extend-ignore = - # See https://github.com/PyCQA/pycodestyle/issues/373 - E203, -statistics = True - [testenv:docs-build] allowlist_externals = make deps =