Skip to content

Commit ede33aa

Browse files
committed
Better type handling
1 parent d543f63 commit ede33aa

7 files changed

Lines changed: 65 additions & 32 deletions

File tree

‎ext4/_compat.py‎

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,27 @@
1+
from typing import Protocol
2+
from typing import runtime_checkable
3+
14
try:
2-
from typing import override
5+
from typing import override # pyright: ignore[reportAssignmentType]
36

47
except ImportError:
58
from typing import Callable
69
from typing import Any
710

8-
def override(fn: Callable[..., Any]):
11+
def override(fn: Callable[..., Any]): # pyright: ignore[reportExplicitAny]
912
return fn
1013

1114

12-
__all__ = ["override"]
15+
@runtime_checkable
16+
class ReadableStream(Protocol):
17+
def read(self, size: int | None = -1, /) -> bytes: ...
18+
19+
def tell(self) -> int: ...
20+
21+
22+
@runtime_checkable
23+
class PeekableStream(ReadableStream, Protocol):
24+
def peek(self, size: int = 0, /) -> bytes: ...
25+
26+
27+
__all__ = ["override", "ReadableStream"]

‎ext4/block.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ def tell(self) -> int:
9191
return self.cursor
9292

9393
@override
94-
def read(self, size: int = -1) -> bytes:
95-
if size < 0:
94+
def read(self, size: int | None = -1) -> bytes:
95+
if size is None or size < 0:
9696
size = len(self) - self.cursor
9797

9898
data = self.peek(size)

‎ext4/inode.py‎

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from __future__ import annotations
22

3+
from codecs import _ReadableStream
34
import io
45
import warnings
56

@@ -9,7 +10,10 @@
910
from ctypes import c_uint16
1011
from ctypes import sizeof
1112

13+
from typing import cast
14+
1215
from ._compat import override
16+
from ._compat import ReadableStream
1317

1418
from .struct import crc32c
1519
from .struct import Ext4Struct
@@ -208,11 +212,11 @@ def block_size(self):
208212
return self.volume.block_size
209213

210214
@property
211-
def i_size(self):
215+
def i_size(self) -> int:
212216
return self.i_size_high << 32 | self.i_size_lo
213217

214218
@property
215-
def i_file_acl(self):
219+
def i_file_acl(self) -> int:
216220
return self.osd2.linux2.l_i_file_acl_high << 32 | self.i_file_acl_lo
217221

218222
@property
@@ -303,24 +307,35 @@ def headers(self):
303307
def indices(self):
304308
return self.tree.indices
305309

306-
def _open(self, mode: str = "rb", encoding: None = None, newline: None = None):
310+
def _open(
311+
self, mode: str = "rb", encoding: None = None, newline: None = None
312+
) -> ReadableStream:
307313
if mode != "rb" or encoding is not None or newline is not None:
308314
raise NotImplementedError()
309315

310316
if self.is_inline:
311317
self.volume.seek(self.offset + Inode.i_block.offset)
312-
data = self.volume.read(self.i_size)
318+
data = cast(bytes, self.volume.read(self.i_size))
313319
return io.BytesIO(data)
314320

315321
return BlockIO(self)
316322

317-
def open(self, mode="rb", encoding=None, newline=None):
323+
def open(
324+
self,
325+
mode: str = "rb", # pyright: ignore[reportUnusedParameter]
326+
encoding: None = None, # pyright: ignore[reportUnusedParameter]
327+
newline: None = None, # pyright: ignore[reportUnusedParameter]
328+
) -> io.RawIOBase:
318329
raise NotImplementedError()
319330

320331
@property
321332
def xattrs(self):
322-
inline_offset = self.offset + self.EXT2_GOOD_OLD_INODE_SIZE + self.i_extra_isize
323-
inline_size = self.offset + self.superblock.s_inode_size - inline_offset
333+
inline_offset = cast(
334+
int, self.offset + self.EXT2_GOOD_OLD_INODE_SIZE + self.i_extra_isize
335+
)
336+
inline_size = cast(
337+
int, self.offset + self.superblock.s_inode_size - inline_offset
338+
)
324339
if inline_size > sizeof(ExtendedAttributeIBodyHeader):
325340
try:
326341
header = ExtendedAttributeIBodyHeader(self, inline_offset, inline_size)
@@ -364,7 +379,7 @@ class File(Inode):
364379
@override
365380
def open(
366381
self, mode: str = "rb", encoding: None = None, newline: None = None
367-
) -> io.BytesIO | BlockIO:
382+
) -> io.RawIOBase:
368383
return self._open(mode, encoding, newline)
369384

370385

‎ext4/struct.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ def to_hex(data):
2525

2626

2727
class Ext4Struct(LittleEndianStructure):
28-
def __init__(self, volume, offset):
28+
def __init__(self, volume, offset: int):
2929
super().__init__()
3030
self.volume = volume
31-
self.offset = offset
31+
self.offset: int = offset
3232
self.read_from_volume()
3333
self.verify()
3434

‎ext4/volume.py‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66

77
from uuid import UUID
88
from pathlib import PurePosixPath
9-
from typing import Callable
109

1110
from cachetools import cached
1211
from cachetools import LRUCache
1312

13+
from ._compat import PeekableStream
1414
from .enum import EXT4_INO
1515
from .superblock import Superblock
1616
from .inode import Inode
@@ -41,7 +41,7 @@ def group(self, index):
4141
return group_index, table_entry_index
4242

4343
@cached(cache=LRUCache(maxsize=32))
44-
def offset(self, index):
44+
def offset(self, index) -> int:
4545
group_index, table_entry_index = self.group(index)
4646
table_offset = (
4747
self.volume.group_descriptors[group_index].bg_inode_table * self.block_size
@@ -57,7 +57,7 @@ def __getitem__(self, index):
5757
class Volume(object):
5858
def __init__(
5959
self,
60-
stream: io.Reader[bytes],
60+
stream: PeekableStream,
6161
offset=0,
6262
ignore_flags=False,
6363
ignore_magic=False,
@@ -69,7 +69,7 @@ def __init__(
6969
if not hasattr(stream, name):
7070
errors.append(f"{name} method missing")
7171

72-
elif not isinstance(getattr(stream, name), Callable):
72+
elif not callable(getattr(stream, name)): # pyright: ignore[reportAny]
7373
errors.append(f"{name} is not a method")
7474

7575
if errors:

‎ext4/xattr.py‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from ctypes import c_uint8
66
from ctypes import sizeof
77

8+
from typing import cast
9+
810
from ._compat import override
911

1012
from .enum import EXT4_FEATURE_INCOMPAT
@@ -69,15 +71,15 @@ def __iter__(self):
6971
warnings.warn(message, RuntimeWarning)
7072

7173
# TODO determine if e_value_size or i_size are required to limit results?
72-
value = inode.open().read()
74+
value = cast(bytes, inode.open().read())
7375

7476
elif entry.e_value_size != 0:
7577
value_offset = self.value_offset(entry)
7678
if value_offset + entry.e_value_size > self.offset + self.data_size:
7779
value = b""
7880
else:
7981
self.volume.seek(value_offset)
80-
value = self.volume.read(entry.e_value_size)
82+
value = cast(bytes, self.volume.read(entry.e_value_size))
8183
else:
8284
value = b""
8385

@@ -160,7 +162,7 @@ def size(self):
160162
return sizeof(self) + self.e_name_len
161163

162164
@property
163-
def name_str(self):
165+
def name_str(self) -> str:
164166
name_index = self.e_name_index
165167
if 0 > name_index or name_index >= len(ExtendedAttributeEntry.NAME_INDICES):
166168
msg = f"Unknown attribute prefix {self.e_name_index:d}"

‎test.py‎

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22

33
import os
44
import sys
5-
import ext4
5+
import ext4 # pyright: ignore[reportImplicitRelativeImport]
66

77
from typing import cast
88
from typing import Callable
9+
from typing import Any
910

1011
FAILED = False
1112

@@ -21,29 +22,29 @@ def test_path_tuple(path: str | bytes, expected: tuple[bytes, ...]):
2122
print("pass")
2223

2324
except Exception as e:
24-
FAILED = True
25+
FAILED = True # pyright: ignore[reportConstantRedefinition]
2526
print("fail")
2627
print(" ", end="")
2728
print(e)
2829

2930

30-
def _assert(source: str, debug: Callable[[], str] | None = None):
31+
def _assert(source: str, debug: Callable[[], Any] | None = None): # pyright: ignore[reportExplicitAny]
3132
global FAILED
3233
print(f"check {source}: ", end="")
3334
if eval(source):
3435
print("pass")
3536
return
3637

37-
FAILED = True
38+
FAILED = True # pyright: ignore[reportConstantRedefinition]
3839
print("fail")
3940
if debug is not None:
4041
print(f" {debug()}")
4142

4243

4344
print("check ext4.Volume stream validation", end="")
4445
try:
45-
ext4.Volume(1)
46-
FAILED = True
46+
_ = ext4.Volume(1) # pyright: ignore[reportArgumentType]
47+
FAILED = True # pyright: ignore[reportConstantRedefinition]
4748
print("fail")
4849

4950
except ext4.InvalidStreamException:
@@ -63,14 +64,14 @@ def _assert(source: str, debug: Callable[[], str] | None = None):
6364
try:
6465
print("check MagicError: ", end="")
6566
_ = ext4.Volume(f, offset=0)
66-
FAILED = True
67+
FAILED = True # pyright: ignore[reportConstantRedefinition]
6768
print("fail")
6869
print(" MagicError not raised")
6970
except ext4.struct.MagicError:
7071
print("pass")
7172

7273
except Exception as e:
73-
FAILED = True
74+
FAILED = True # pyright: ignore[reportConstantRedefinition]
7475
print("fail")
7576
print(" ", end="")
7677
print(e)
@@ -84,7 +85,7 @@ def _assert(source: str, debug: Callable[[], str] | None = None):
8485
print("pass")
8586

8687
except ext4.struct.ChecksumError as e:
87-
FAILED = True
88+
FAILED = True # pyright: ignore[reportConstantRedefinition]
8889
print("fail")
8990
print(" ", end="")
9091
print(e)
@@ -111,7 +112,7 @@ def _assert(source: str, debug: Callable[[], str] | None = None):
111112
b = inode.open()
112113
data = b"hello world1\n"
113114
for x in range(1, 15):
114-
_assert(f"b.peek({x}) == {data[:x]}", lambda: b.peek(x))
115+
_assert(f"b.read({x}) == {data[:x]}", lambda: b.read(x))
115116

116117
if FAILED:
117118
sys.exit(1)

0 commit comments

Comments
 (0)