From d7e053285ba70e8b849204a07e006976cc9e8606 Mon Sep 17 00:00:00 2001 From: Marco Gorelli <33491632+MarcoGorelli@users.noreply.github.com> Date: Tue, 17 Jun 2025 14:16:30 +0100 Subject: [PATCH] inline type stubs --- .github/workflows/test.yml | 28 +++++ olefile-stubs/__init__.pyi | 2 + olefile-stubs/olefile.pyi | 249 +++++++++++++++++++++++++++++++++++++ olefile/olefile.py | 12 +- 4 files changed, 285 insertions(+), 6 deletions(-) create mode 100644 olefile-stubs/__init__.pyi create mode 100644 olefile-stubs/olefile.pyi diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2affb78..16b8417 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -39,3 +39,31 @@ jobs: with: flags: ${{ matrix.os }} name: ${{ matrix.os }} Python ${{ matrix.python-version }} + + stubtest: + runs-on: ubuntu-latest + strategy: + fail-fast: false + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python 3.x + uses: actions/setup-python@v4 + with: + python-version: 3.x + allow-prereleases: true + cache: pip + cache-dependency-path: setup.cfg + + - name: Install dependencies + run: | + python -m pip install -U pip + python -m pip install -U mypy + + - name: Stubtest + run: | + python -m pip install -e . + python -m mypy.stubtest olefile + python -m mypy olefile tests + diff --git a/olefile-stubs/__init__.pyi b/olefile-stubs/__init__.pyi new file mode 100644 index 0000000..4697cce --- /dev/null +++ b/olefile-stubs/__init__.pyi @@ -0,0 +1,2 @@ +from .olefile import * +from .olefile import __all__ as __all__, __author__ as __author__, __date__ as __date__, __version__ as __version__ diff --git a/olefile-stubs/olefile.pyi b/olefile-stubs/olefile.pyi new file mode 100644 index 0000000..3ea1ab7 --- /dev/null +++ b/olefile-stubs/olefile.pyi @@ -0,0 +1,249 @@ +import array +import datetime +import io +import logging +import traceback +from collections.abc import Sequence +from typing import IO, AnyStr, Generic +from typing_extensions import Self, TypeAlias + +__date__: str +__version__: str +__author__: str + +__all__ = [ + "isOleFile", + "OleFileIO", + "OleMetadata", + "enable_logging", + "MAGIC", + "STGTY_EMPTY", + "STGTY_STREAM", + "STGTY_STORAGE", + "STGTY_ROOT", + "STGTY_PROPERTY", + "STGTY_LOCKBYTES", + "MINIMAL_OLEFILE_SIZE", + "DEFECT_UNSURE", + "DEFECT_POTENTIAL", + "DEFECT_INCORRECT", + "DEFECT_FATAL", + "DEFAULT_PATH_ENCODING", + "MAXREGSECT", + "DIFSECT", + "FATSECT", + "ENDOFCHAIN", + "FREESECT", + "MAXREGSID", + "NOSTREAM", + "UNKNOWN_SIZE", + "WORD_CLSID", + "OleFileIONotClosed", +] + +UINT32: str + +DEFAULT_PATH_ENCODING: str | None + +def get_logger(name: str, level: int = ...) -> logging.Logger: ... + +log: logging.Logger + +def enable_logging() -> None: ... + +MAGIC: bytes + +MAXREGSECT: int +DIFSECT: int +FATSECT: int +ENDOFCHAIN: int +FREESECT: int + +MAXREGSID: int +NOSTREAM: int + +STGTY_EMPTY: int +STGTY_STORAGE: int +STGTY_STREAM: int +STGTY_LOCKBYTES: int +STGTY_PROPERTY: int +STGTY_ROOT: int + +UNKNOWN_SIZE: int + +VT_EMPTY: int +VT_NULL: int +VT_I2: int +VT_I4: int +VT_R4: int +VT_R8: int +VT_CY: int +VT_DATE: int +VT_BSTR: int +VT_DISPATCH: int +VT_ERROR: int +VT_BOOL: int +VT_VARIANT: int +VT_UNKNOWN: int +VT_DECIMAL: int +VT_I1: int +VT_UI1: int +VT_UI2: int +VT_UI4: int +VT_I8: int +VT_UI8: int +VT_INT: int +VT_UINT: int +VT_VOID: int +VT_HRESULT: int +VT_PTR: int +VT_SAFEARRAY: int +VT_CARRAY: int +VT_USERDEFINED: int +VT_LPSTR: int +VT_LPWSTR: int +VT_FILETIME: int +VT_BLOB: int +VT_STREAM: int +VT_STORAGE: int +VT_STREAMED_OBJECT: int +VT_STORED_OBJECT: int +VT_BLOB_OBJECT: int +VT_CF: int +VT_CLSID: int +VT_VECTOR: int + +VT: dict[int, str] + +WORD_CLSID: str + +DEFECT_UNSURE: int +DEFECT_POTENTIAL: int +DEFECT_INCORRECT: int +DEFECT_FATAL: int + +MINIMAL_OLEFILE_SIZE: int + +def isOleFile(filename: IO[bytes] | bytes | str | None = ..., data: bytes | None = ...) -> bool: ... +def i8(c: bytes | int) -> int: ... +def i16(c: bytes, o: int = ...) -> int: ... +def i32(c: bytes, o: int = ...) -> int: ... +def _clsid(clsid: bytes) -> str: ... +def filetime2datetime(filetime: int) -> datetime.datetime: ... + +class OleFileError(IOError): ... +class NotOleFileError(OleFileError): ... + +class OleMetadata: + SUMMARY_ATTRIBS: list[str] + DOCSUM_ATTRIBS: list[str] + + def __init__(self) -> None: ... + def parse_properties(self, ole_file: OleFileIO[AnyStr]) -> None: ... + def dump(self) -> None: ... + +class OleFileIONotClosed(RuntimeWarning): + def __init__(self, stack_of_open: traceback.FrameSummary | None = ...) -> None: ... + +class OleStream(io.BytesIO): + def __init__( + self, + fp: IO[bytes], + sect: int, + size: int, + offset: int, + sectorsize: int, + fat: list[int], + filesize: int, + olefileio: OleFileIO[AnyStr], + ) -> None: ... + +class OleDirectoryEntry(Generic[AnyStr]): + STRUCT_DIRENTRY: str + DIRENTRY_SIZE: int + clsid: str + + def __init__(self, entry: bytes, sid: int, ole_file: OleFileIO[AnyStr]) -> None: ... + def build_sect_chain(self, ole_file: OleFileIO[AnyStr]) -> None: ... + def build_storage_tree(self) -> None: ... + def append_kids(self, child_sid: int) -> None: ... + def __eq__(self, other: OleDirectoryEntry[AnyStr]) -> bool: ... # type: ignore[override] + def __lt__(self, other: OleDirectoryEntry[AnyStr]) -> bool: ... + def __ne__(self, other: OleDirectoryEntry[AnyStr]) -> bool: ... # type: ignore[override] + def __le__(self, other: OleDirectoryEntry[AnyStr]) -> bool: ... + def dump(self, tab: int = ...) -> None: ... + def getmtime(self) -> datetime.datetime | None: ... + def getctime(self) -> datetime.datetime | None: ... + +_Property: TypeAlias = int | str | bytes | bool | None + +class OleFileIO(Generic[AnyStr]): + root: OleDirectoryEntry[AnyStr] | None + + def __init__( + self, + filename: IO[bytes] | AnyStr | None = ..., + raise_defects: int = ..., + write_mode: bool = ..., + debug: bool = ..., + path_encoding: str | None = ..., # noqa: Y011 + ) -> None: ... + def __del__(self) -> None: ... + def __enter__(self) -> Self: ... + def __exit__(self, *args: object) -> None: ... + def _raise_defect( + self, defect_level: int, message: str, exception_type: type[Exception] = ... # noqa: Y011 + ) -> None: ... + def _decode_utf16_str(self, utf16_str: bytes, errors: str = ...) -> str | bytes: ... + def open(self, filename: IO[bytes] | AnyStr, write_mode: bool = ...) -> None: ... + def close(self) -> None: ... + def _close(self, warn: bool = ...) -> None: ... + def _check_duplicate_stream(self, first_sect: int, minifat: bool = ...) -> None: ... + def dumpfat(self, fat: Sequence[int], firstindex: int = ...) -> None: ... + def dumpsect(self, sector: bytes, firstindex: int = ...) -> None: ... + def sect2array(self, sect: bytes) -> Sequence[int]: ... + def loadfat_sect(self, sect: bytes | array.array[int]) -> int | None: ... + def loadfat(self, header: bytes) -> None: ... + def loadminifat(self) -> None: ... + def getsect(self, sect: int) -> bytes: ... + def write_sect(self, sect: int, data: bytes, padding: bytes = ...) -> None: ... + def _write_mini_sect(self, fp_pos: int, data: bytes, padding: bytes = ...) -> None: ... + def loaddirectory(self, sect: int) -> None: ... + def _load_direntry(self, sid: int) -> OleDirectoryEntry[AnyStr]: ... + def dumpdirectory(self) -> None: ... + def _open(self, start: int, size: int = ..., force_FAT: bool = ...) -> OleStream: ... + def _list( + self, + files: list[list[AnyStr]], + prefix: list[AnyStr], + node: OleDirectoryEntry[AnyStr], + streams: bool = ..., + storages: bool = ..., + ) -> None: ... + def listdir(self, streams: bool = ..., storages: bool = ...) -> list[list[AnyStr]]: ... + def _find(self, filename: str | Sequence[str]) -> int: ... + def openstream(self, filename: AnyStr | Sequence[AnyStr]) -> OleStream: ... + def _write_mini_stream(self, entry: OleDirectoryEntry[AnyStr], data_to_write: bytes) -> None: ... + def write_stream(self, stream_name: str | Sequence[str], data: bytes) -> None: ... + def get_type(self, filename: AnyStr | Sequence[AnyStr]) -> bool | int: ... + def getclsid(self, filename: AnyStr | Sequence[AnyStr]) -> str: ... + def getmtime(self, filename: AnyStr | Sequence[AnyStr]) -> datetime.datetime | None: ... + def getctime(self, filename: AnyStr | Sequence[AnyStr]) -> datetime.datetime | None: ... + def exists(self, filename: AnyStr | Sequence[AnyStr]) -> bool: ... + def get_size(self, filename: AnyStr | Sequence[AnyStr]) -> int: ... + def get_rootentry_name(self) -> bytes: ... + def getproperties( + self, filename: AnyStr | Sequence[AnyStr], convert_time: bool = ..., no_conversion: list[int] | None = ... + ) -> dict[int, list[_Property] | _Property]: ... + def _parse_property( + self, s: bytes, offset: int, property_id: int, property_type: int, convert_time: bool, no_conversion: list[int] + ) -> list[_Property] | _Property: ... + def _parse_property_basic( + self, s: bytes, offset: int, property_id: int, property_type: int, convert_time: bool, no_conversion: list[int] + ) -> tuple[_Property, int]: ... + def get_metadata(self) -> OleMetadata: ... + def get_userdefined_properties( + self, filename: AnyStr | Sequence[AnyStr], convert_time: bool = ..., no_conversion: list[int] | None = ... + ) -> list[dict[str, bytes | int | None]]: ... + +def main() -> None: ... diff --git a/olefile/olefile.py b/olefile/olefile.py index 60f8963..a05eb78 100644 --- a/olefile/olefile.py +++ b/olefile/olefile.py @@ -27,7 +27,7 @@ # (main requirement to be compatible with Python 3.x) # The comment on the line below should be printed on Python 2.5 or older: from __future__ import print_function # This version of olefile requires Python 2.7 or 3.5+. - +import sys #--- LICENSE ------------------------------------------------------------------ @@ -112,10 +112,10 @@ long = int # Need to make sure we use xrange both on Python 2 and 3.x: -try: +if sys.version_info < (3,): # on Python 2 we need xrange: iterrange = xrange -except Exception: +else: # no xrange, for Python 3 it was renamed as range: iterrange = range @@ -146,9 +146,9 @@ # [PL] These workarounds were inspired from the Path module # (see http://www.jorendorff.com/articles/python/path/) # TODO: remove the use of basestring, as it was removed in Python 3 -try: +if sys.version_info < (3,): basestring -except NameError: +else: basestring = str if sys.version_info[0] < 3: @@ -330,7 +330,7 @@ def isOleFile (filename=None, data=None): return False -if bytes is str: +if sys.version_info < (3,): # version for Python 2.x def i8(c): return ord(c)