diff --git a/binapy/binapy.py b/binapy/binapy.py index 8f83961..88c479f 100644 --- a/binapy/binapy.py +++ b/binapy/binapy.py @@ -4,6 +4,7 @@ import re import secrets +from contextlib import suppress from functools import wraps from typing import ( Any, @@ -117,13 +118,13 @@ def re_match(self, pattern: str, encoding: str = "ascii") -> str: the decoded, matching `str` Raises: - ValueError: if the decoded str doesn't match `pattern` + ValueError: if the decoded str does not match `pattern` """ res = self.decode(encoding) if re.match(pattern, res): return res - msg = f"This value doesn't match pattern {pattern}" + msg = f"This value does not match pattern {pattern}" raise ValueError(msg) def text(self, encoding: str = "ascii") -> str: @@ -354,7 +355,7 @@ def _get_checker(cls, extension_name: str) -> Callable[..., bool]: extension_methods = cls._get_extension_methods(extension_name) method = extension_methods.get("check") if method is None: - msg = f"Extension {extension_name} doesn't have a checker method" + msg = f"Extension '{extension_name}' does not have a checker method" raise NotImplementedError(msg) return method @@ -363,7 +364,7 @@ def _get_decoder(cls, extension_name: str) -> Callable[..., BinaPy]: extension_methods = cls._get_extension_methods(extension_name) method = extension_methods.get("decode") if method is None: - msg = f"Extension {extension_name} doesn't have a decode method" + msg = f"Extension '{extension_name}' does not have a decode method" raise NotImplementedError(msg) return method @@ -372,7 +373,7 @@ def _get_encoder(cls, extension_name: str) -> Callable[..., BinaPy]: extension_methods = cls._get_extension_methods(extension_name) method = extension_methods.get("encode") if method is None: - msg = f"Extension {extension_name} doesn't have an encode method" + msg = f"Extension '{extension_name}' does not have an encode method" raise NotImplementedError(msg) return method @@ -381,7 +382,7 @@ def _get_parser(cls, extension_name: str) -> Callable[..., Any]: extension_methods = cls._get_extension_methods(extension_name) method = extension_methods.get("parse") if method is None: - msg = f"Extension {extension_name} doesn't have a parse method" + msg = f"Extension '{extension_name}' does not have a parse method" raise NotImplementedError(msg) return method @@ -390,7 +391,7 @@ def _get_serializer(cls, extension_name: str) -> Callable[..., BinaPy]: extension_methods = cls._get_extension_methods(extension_name) method = extension_methods.get("serialize") if method is None: - msg = f"Extension {extension_name} doesn't have a serialize method" + msg = f"Extension '{extension_name}' does not have a serialize method" raise NotImplementedError(msg) return method @@ -440,12 +441,12 @@ def decode_from(self, name: str, *args: Any, **kwargs: Any) -> BinaPy: return decoder(self, *args, **kwargs) - def check(self, name: str, *, decode: bool = False, raise_on_error: bool = False) -> bool: # noqa: PLR0912 + def check(self, name: str, *, decode: bool = False, raise_on_error: bool = False) -> bool: """Check that this BinaPy conforms to a given format extension. Args: name: the name of the extension to check - decode: if `True`, and the given extension doesn't have a checker method, + decode: if `True`, and the given extension does not have a checker method, try to decode this BinaPy using the decoder method to check if that works. raise_on_error: if `True`, exceptions from the checker method, if any, will be raised instead of returning `False`. @@ -454,7 +455,7 @@ def check(self, name: str, *, decode: bool = False, raise_on_error: bool = False a boolean, that is True if this BinaPy conforms to the given extension format, False otherwise. """ - # raises an exception in case the extension doesn't exist + # raises an exception in case the extension does not exist self._get_extension_methods(name) try: @@ -468,20 +469,19 @@ def check(self, name: str, *, decode: bool = False, raise_on_error: bool = False raise exc from exc return False except NotImplementedError: - try: - # if checker is not implemented and decode is True, try to decode instead - if decode: - decoder = self._get_decoder(name) - try: - decoder(self) - except Exception as exc: - if raise_on_error: - raise exc from exc - return False - else: - return True - except NotImplementedError: - return False + # if checker is not implemented and decode is True, try to decode instead + if decode: + decoder = self._get_decoder(name) + try: + decoder(self) + except Exception as exc: + if raise_on_error: + raise exc from exc + return False + else: + return True + else: + raise return False def check_all(self, *, decode: bool = False) -> list[str]: @@ -498,9 +498,10 @@ def check_all(self, *, decode: bool = False) -> list[str]: def get_results() -> Iterator[str]: for name in self.extensions: - success = self.check(name, decode=decode) - if success is True: - yield name + with suppress(NotImplementedError): + success = self.check(name, decode=decode) + if success is True: + yield name return list(get_results()) diff --git a/tests/test_binapy.py b/tests/test_binapy.py index a46bcbf..09e2c1b 100644 --- a/tests/test_binapy.py +++ b/tests/test_binapy.py @@ -120,6 +120,30 @@ def test_unknown_features() -> None: with pytest.raises(NotImplementedError): BinaPy.serialize_to("something_not_known", {"foo": "bar"}) + @binapy_encoder("something_known") + def encode_something(bp: bytes) -> bytes: + return bp + + with pytest.raises(NotImplementedError, match="Extension 'something_known' does not have a decode method"): + bp.decode_from("something_known") + + with pytest.raises(NotImplementedError, match="Extension 'something_known' does not have a parse method"): + bp.parse_from("something_known") + + with pytest.raises(NotImplementedError, match="Extension 'something_known' does not have a serialize method"): + bp.serialize_to("something_known") + + with pytest.raises(NotImplementedError, match="Extension 'something_known' does not have a checker method"): + bp.check("something_known") + + @binapy_decoder("something_else") + def decode_something_else(bp: bytes) -> bytes: + return bp + + with pytest.raises(NotImplementedError, match="Extension 'something_else' does not have an encode method"): + bp.encode_to("something_else") + + def test_exceptions() -> None: @binapy_decoder("some_feature") @@ -128,7 +152,8 @@ def decode_some_feature(bp: BinaPy) -> BinaPy: bp = BinaPy() - assert bp.check("some_feature") is False + with pytest.raises(NotImplementedError): + bp.check("some_feature") assert bp.check("some_feature", decode=True) is True @binapy_decoder("other_feature") @@ -136,7 +161,8 @@ def check_other_feature(bp: BinaPy) -> bool: raise ValueError() bp = BinaPy() - assert bp.check("other_feature") is False + with pytest.raises(NotImplementedError): + bp.check("other_feature") assert bp.check("other_feature", decode=True) is False with pytest.raises(ValueError): diff --git a/tests/test_json.py b/tests/test_json.py index e4c91ff..4f190d5 100644 --- a/tests/test_json.py +++ b/tests/test_json.py @@ -1,5 +1,7 @@ import uuid +from collections import UserDict from datetime import datetime, timedelta, timezone +from typing import Iterable from binapy import BinaPy @@ -14,6 +16,12 @@ def test_json() -> None: def test_json_encoder() -> None: """Datetimes are serialized to integer epoch timestamps, but integer stay integers when parsed.""" + + user_dict = UserDict({"my": "dict"}) + def iterable() -> Iterable[int]: + for i in range(5): + yield i + bp = BinaPy.serialize_to( "json", { @@ -26,15 +34,19 @@ def test_json_encoder() -> None: second=40, tzinfo=timezone(timedelta(seconds=0)), ), - "foo": uuid.UUID("71509952-ec4f-4854-84e7-fa452994b51d"), + "uuid": uuid.UUID("71509952-ec4f-4854-84e7-fa452994b51d"), + "user_dict": user_dict, + "iterable": iterable() }, sort_keys=True, ) - assert bp == b'{"foo":"71509952-ec4f-4854-84e7-fa452994b51d","iat":1600000000}' + assert bp == b'{"iat":1600000000,"iterable":[0,1,2,3,4],"user_dict":{"my":"dict"},"uuid":"71509952-ec4f-4854-84e7-fa452994b51d"}' assert bp.parse_from("json") == { "iat": 1600000000, - "foo": "71509952-ec4f-4854-84e7-fa452994b51d", + "iterable": [0, 1, 2, 3, 4], + "uuid": "71509952-ec4f-4854-84e7-fa452994b51d", + "user_dict": {"my": "dict"} }