From cfd4dd227d422593f4b6fb973727909e06fbd94a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20Vask=C3=B3?= <1771332+vlaci@users.noreply.github.com> Date: Tue, 21 Jan 2025 16:41:09 +0100 Subject: [PATCH 1/6] chore(file_utils): fix `reportIncompatibleMethodOverride` lints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This check is currently turned off in our `basic` `typeCheckingMode` profile, but is enabled in the `standard` profile. Things done here: - matched the signature of `__exit__` and `seekable` to `mmap`'s type stub. - indicate that `seekable` is present in `mmap` in Python 3.13+ - ignored the `seek` method, as we explicitly wanted it to return the file position similarly to the standard file api Error: unblob/file_utils.py unblob/file_utils.py:70:9 - error: Method "seek" overrides class "mmap" in an incompatible manner   Return type mismatch: base method returns type "None", override returns type "int"     "int" is not assignable to "None" (reportIncompatibleMethodOverride) unblob/file_utils.py:94:9 - error: Method "__exit__" overrides class "mmap" in an incompatible manner   Positional parameter count mismatch; base method has 2, but override has 4   Parameter "args" is missing in override (reportIncompatibleMethodOverride) unblob/file_utils.py:103:9 - error: Method "seekable" overrides class "mmap" in an incompatible manner   Return type mismatch: base method returns type "Literal[True]", override returns type "bool"     "bool" is not assignable to type "Literal[True]" (reportIncompatibleMethodOverride) --- unblob/file_utils.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/unblob/file_utils.py b/unblob/file_utils.py index 65f1cfcb97..dfdf1cb5ae 100644 --- a/unblob/file_utils.py +++ b/unblob/file_utils.py @@ -8,6 +8,7 @@ import re import shutil import struct +import sys import unicodedata from collections.abc import Iterable, Iterator from pathlib import Path @@ -67,7 +68,7 @@ def from_path(cls, path: Path, access=mmap.ACCESS_READ): m.access = access return m - def seek(self, pos: int, whence: int = os.SEEK_SET) -> int: + def seek(self, pos: int, whence: int = os.SEEK_SET) -> int: # pyright: ignore[reportIncompatibleMethodOverride] try: super().seek(pos, whence) except ValueError as e: @@ -91,7 +92,7 @@ def size(self) -> int: def __enter__(self): return self - def __exit__(self, _exc_type, _exc_val, _exc_tb): + def __exit__(self, *args): self.close() def readable(self) -> bool: @@ -100,8 +101,10 @@ def readable(self) -> bool: def writable(self) -> bool: return self.access in (mmap.ACCESS_WRITE, mmap.ACCESS_COPY) - def seekable(self) -> bool: - return True # Memory-mapped files are always seekable + if sys.version_info < (3, 13): + + def seekable(self) -> Literal[True]: + return True # Memory-mapped files are always seekable class OffsetFile: From 848c6dfdd2c8298e5903db1a8bc796f38362375a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20Vask=C3=B3?= <1771332+vlaci@users.noreply.github.com> Date: Tue, 21 Jan 2025 16:50:57 +0100 Subject: [PATCH 2/6] chore(models/Report): fix `reportIncompatibleMethodOverride` lints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This check is currently turned off in our `basic` `typeCheckingMode` profile, but is enabled in the `standard` profile. Things done here: - matched the signature of `default` with `JSONEncoder.default` Error: unblob/models.py unblob/models.py:258:9 - error: Method "default" overrides class "JSONEncoder" in an incompatible manner   Parameter 2 name mismatch: base parameter is named "o", override parameter is named "obj" (reportIncompatibleMethodOverride) --- unblob/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/unblob/models.py b/unblob/models.py index 0bba100e1c..396fc070eb 100644 --- a/unblob/models.py +++ b/unblob/models.py @@ -255,7 +255,8 @@ def get_output_dir(self) -> Optional[Path]: class _JSONEncoder(json.JSONEncoder): - def default(self, obj): + def default(self, o): + obj = o if attrs.has(type(obj)): extend_attr_output = True attr_output = attrs.asdict(obj, recurse=not extend_attr_output) From cb009cb3624ceba8fc122760021e4c0ce321827f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20Vask=C3=B3?= <1771332+vlaci@users.noreply.github.com> Date: Tue, 21 Jan 2025 16:52:53 +0100 Subject: [PATCH 3/6] chore(extractors/command): fix `reportIncompatibleMethodOverride` lints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This check is currently turned off in our `basic` `typeCheckingMode` profile, but is enabled in the `standard` profile. Things done here: - removed the invalid cross inheritance, as the bases conflict on `extract` method Extractor -> Command ^ | x | v v DirectoryExtractor -> MultiFileCommand Error: unblob/extractors/command.py unblob/extractors/command.py:106:9 - error: Method "extract" overrides class "Command" in an incompatible manner   Parameter 2 name mismatch: base parameter is named "inpath", override parameter is named "paths" (reportIncompatibleMethodOverride) --- unblob/extractors/command.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/unblob/extractors/command.py b/unblob/extractors/command.py index ad984ff039..7db354d676 100644 --- a/unblob/extractors/command.py +++ b/unblob/extractors/command.py @@ -102,9 +102,15 @@ def get_dependencies(self) -> list[str]: return [self._executable] -class MultiFileCommand(Command, DirectoryExtractor): +class MultiFileCommand(DirectoryExtractor): + def __init__(self, *args, **kwargs): + self._command = Command(*args, **kwargs) + def extract(self, paths: list[Path], outdir: Path): - return super().extract(paths[0], outdir) + return self._command.extract(paths[0], outdir) + + def get_dependencies(self) -> list[str]: + return self._command.get_dependencies() class InvalidCommandTemplate(ValueError): From 698150126110a163c66bf3e6c4b0048bb52b18f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20Vask=C3=B3?= <1771332+vlaci@users.noreply.github.com> Date: Tue, 21 Jan 2025 17:00:00 +0100 Subject: [PATCH 4/6] chore(models/Handler): fix `reportIncompatibleVariableOverride` lints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This check is currently turned off in our `basic` `typeCheckingMode` profile, but is enabled in the `standard` profile. Things done here: - made the type of `Handler.EXTRACTOR` covariant, so child-classes can further narrow its type Error: unblob/handlers/archive/cpio.py unblob/handlers/archive/cpio.py:413:5 - error: "EXTRACTOR" overrides symbol of same name in class "Handler"   Variable is mutable so its type is invariant     Override type "_CPIOExtractorBase" is not the same as base type "Extractor | None" (reportIncompatibleVariableOverride) --- unblob/models.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/unblob/models.py b/unblob/models.py index 396fc070eb..ed04887bc3 100644 --- a/unblob/models.py +++ b/unblob/models.py @@ -4,7 +4,7 @@ from collections.abc import Iterable from enum import Enum from pathlib import Path -from typing import Optional, TypeVar +from typing import Generic, Optional, TypeVar, Union import attrs from structlog import get_logger @@ -439,7 +439,10 @@ def extract(self, paths: list[Path], outdir: Path) -> Optional[ExtractResult]: return self.EXTRACTOR.extract(paths, outdir) -class Handler(abc.ABC): +TExtractor = TypeVar("TExtractor", bound=Union[None, Extractor]) + + +class Handler(abc.ABC, Generic[TExtractor]): """A file type handler is responsible for searching, validating and "unblobbing" files from Blobs.""" NAME: str @@ -448,12 +451,12 @@ class Handler(abc.ABC): # (e.g. tar magic is in the middle of the header) PATTERN_MATCH_OFFSET: int = 0 - EXTRACTOR: Optional[Extractor] + EXTRACTOR: TExtractor @classmethod def get_dependencies(cls): """Return external command dependencies needed for this handler to work.""" - if cls.EXTRACTOR: + if cls.EXTRACTOR is not None: return cls.EXTRACTOR.get_dependencies() return [] From 013fd70beda696009dd64a5cf4556525203504e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20Vask=C3=B3?= <1771332+vlaci@users.noreply.github.com> Date: Tue, 21 Jan 2025 17:01:32 +0100 Subject: [PATCH 5/6] chore(handlers/gzip): fix `reportIncompatibleMethodOverride` lints MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This check is currently turned off in our `basic` `typeCheckingMode` profile, but is enabled in the `standard` profile. Things done here: - instead of incompatible overriding `read`, added a new `read_all` method, to better express the functionality of that method Error: unblob/handlers/compression/_gzip_reader.py unblob/handlers/compression/_gzip_reader.py:18:9 - error: Method "read" overrides class "DecompressReader" in an incompatible manner   Positional parameter count mismatch; base method has 2, but override has 1   Parameter 2 mismatch: base parameter "size" is keyword parameter, override parameter is position-only (reportIncompatibleMethodOverride) --- unblob/handlers/compression/_gzip_reader.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/unblob/handlers/compression/_gzip_reader.py b/unblob/handlers/compression/_gzip_reader.py index 0a9ac66a8b..effdc7cce3 100644 --- a/unblob/handlers/compression/_gzip_reader.py +++ b/unblob/handlers/compression/_gzip_reader.py @@ -15,7 +15,7 @@ def _add_read_data(self, data): self._crc = zlib.crc32(data, self._crc) self._stream_size = self._stream_size + len(data) - def read(self): + def read_all(self): uncompress = b"" while True: @@ -39,7 +39,7 @@ def read(self): def read_until_eof(self): while not self._decompressor.eof: - self.read() + self.read_all() @property def unused_data(self): From a354a122bb5e5be59f886f2bdb49c871dafdc7e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?L=C3=A1szl=C3=B3=20Vask=C3=B3?= <1771332+vlaci@users.noreply.github.com> Date: Tue, 21 Jan 2025 17:03:38 +0100 Subject: [PATCH 6/6] chore(pyright): enable the `standard` type checking profile --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index ae11b885ad..fc3adc5ae0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -177,7 +177,7 @@ exclude = [ ".venv", "build", ] -typeCheckingMode = "basic" +typeCheckingMode = "standard" [build-system] build-backend = "hatchling.build"