Skip to content

Commit

Permalink
Fix: struct.error when reading ReadStruct-based types #111
Browse files Browse the repository at this point in the history
  • Loading branch information
eigenein committed Apr 25, 2023
1 parent 2f20718 commit 53707b4
Show file tree
Hide file tree
Showing 9 changed files with 431 additions and 489 deletions.
884 changes: 406 additions & 478 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pure_protobuf/descriptors/record.py
Expand Up @@ -56,7 +56,7 @@ class RecordDescriptor(Generic[RecordT]):
This behaves differently from the [`write`][pure_protobuf.descriptors.record.RecordDescriptor.write],
because it's only supposed to read a single record from the stream
(it may be, for example, just one item of a repeated field).
(it may be, for example, just one item of a packed repeated field).
Also, it assumes that the tag has already been read by [`BaseMessage`][base-message].
"""

Expand Down
4 changes: 2 additions & 2 deletions pure_protobuf/helpers/io.py
Expand Up @@ -24,6 +24,6 @@ def read_checked(io: IO[bytes], count: int) -> bytes:
EOFError: the stream has ended
"""
buffer = io.read(count)
if len(buffer) != count:
raise EOFError(f"{count} bytes expected, {len(buffer)} read")
if len(buffer) < count:
raise EOFError(f"{count} bytes expected, but only {len(buffer)} read")
return buffer
2 changes: 2 additions & 0 deletions pure_protobuf/helpers/itertools.py
Expand Up @@ -9,6 +9,8 @@


class ReadCallback(ReprWithInner, Generic[P, R]):
"""Convert a reader, which returns a singular scalar value, into an iterable reader."""

__slots__ = ("inner",)

# noinspection PyProtocol
Expand Down
4 changes: 2 additions & 2 deletions pure_protobuf/interfaces/_repr.py
Expand Up @@ -14,7 +14,7 @@ def __str__(self) -> str:

def __repr__(self) -> str:
try:
orig_class = self.__orig_class__ # type: ignore
orig_class = self.__orig_class__ # type: ignore[attr-defined]
except AttributeError:
args = ""
else:
Expand All @@ -27,7 +27,7 @@ class ReprWithInner(Repr, Protocol):

def __repr__(self) -> str:
try:
orig_class = self.__orig_class__ # type: ignore
orig_class = self.__orig_class__ # type: ignore[attr-defined]
except AttributeError:
args = ""
else:
Expand Down
3 changes: 2 additions & 1 deletion pure_protobuf/io/struct_.py
@@ -1,6 +1,7 @@
from struct import Struct
from typing import IO, Generic, Iterator

from pure_protobuf.helpers.io import read_checked
from pure_protobuf.interfaces._repr import ReprWithInner
from pure_protobuf.interfaces._vars import RecordT_co, RecordT_contra
from pure_protobuf.interfaces.read import Read
Expand All @@ -18,7 +19,7 @@ def __init__(self, format_: str) -> None: # noqa: D107

def __call__(self, io: IO[bytes]) -> Iterator[RecordT_co]:
inner = self.inner
yield from inner.unpack(io.read(inner.size))
yield from inner.unpack(read_checked(io, inner.size))


class WriteStruct(Write[RecordT_contra], ReprWithInner, Generic[RecordT_contra]):
Expand Down
7 changes: 6 additions & 1 deletion pure_protobuf/io/wrappers.py
Expand Up @@ -52,7 +52,12 @@ def __call__(self, io: IO[bytes], actual_wire_type: WireType) -> Iterator[Record


class ReadLengthDelimited(Read[RecordT], ReprWithInner):
"""Wraps the inner reader on a length-delimited buffer."""
"""
Make the inner reader length-delimited.
This reader wraps the inner reader so that it would first read a byte string,
and then call the inner reader on that byte string.
"""

__slots__ = ("inner",)

Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Expand Up @@ -43,7 +43,7 @@ optional = true

[tool.poetry.group.dev.dependencies]
black = "^22.12.0"
mypy = "^1.0.0"
mypy = "^1.2.0"
pydantic = "^1.10.4"
pytest = "^7.2.0"
pytest-benchmark = "^4.0.0"
Expand Down
12 changes: 9 additions & 3 deletions tests/descriptors/test_record.py
Expand Up @@ -33,9 +33,15 @@ def test_from_inner_hint_unsupported(inner_hint: Any) -> None:
(1.0, b"\x00\x00\x80\x3F"),
],
)
def test_struct(value: float, encoded: bytes) -> None:
assert to_bytes(FLOAT_DESCRIPTOR.write, value) == encoded
assert next(FLOAT_DESCRIPTOR.read(BytesIO(encoded), WireType.I32)) == value
class TestStruct:
def test_write(self, value: float, encoded: bytes) -> None:
assert to_bytes(FLOAT_DESCRIPTOR.write, value) == encoded

def test_read(self, value: float, encoded: bytes) -> None:
io = BytesIO(encoded)
assert next(FLOAT_DESCRIPTOR.read(io, WireType.I32)) == value
with raises(EOFError):
next(FLOAT_DESCRIPTOR.read(io, WireType.I32))


# noinspection PyArgumentList
Expand Down

0 comments on commit 53707b4

Please sign in to comment.