~chiefnoah/pybare

bf1f2a2243e691f1f6ec79386eda17cfc9105aa0 — Noah Pederson 8 months ago 1d7d484
Autoformat
3 files changed, 99 insertions(+), 41 deletions(-)

M bare/encoder.py
M bare/test_encoder.py
M bare/types.py
M bare/encoder.py => bare/encoder.py +17 -12
@@ 207,7 207,6 @@ class Struct(ABC):
            vals[field] = val.value
        return cls(**vals)


    @classmethod
    def unpack(cls, data: typing.Union[typing.BinaryIO, bytes]):
        """


@@ 251,6 250,7 @@ class Struct(ABC):
            output[name] = field.to_dict(value=val)
        return output


class _ValidatedList(UserList):
    def __init__(self, *args, instance: "Array" = None, **kwargs):
        if instance is None:


@@ 284,9 284,7 @@ class Array(Field):
    _length = 0  # zero means variable length
    _default = None

    def __init__(
        self, type: typing.Type[Field] = None, length=0, values=None
    ):
    def __init__(self, type: typing.Type[Field] = None, length=0, values=None):
        if type is not None:
            if inspect.isclass(type):
                self._type = type()


@@ 342,14 340,16 @@ class Array(Field):
                default = self._type._default
            elif isinstance(self._type, Struct):
                default = self._type.__class__()
            value.extend([default] * (self._length - len(value))) # pad with default values
            value.extend(
                [default] * (self._length - len(value))
            )  # pad with default values
        for item in value:
            if isinstance(item, Field):
                self._type._pack(fp, item.value)
            else:
                self._type._pack(fp, item)

    def _unpack(self, fp: typing.BinaryIO) -> 'Array':
    def _unpack(self, fp: typing.BinaryIO) -> "Array":
        if self._length == 0:
            length = _read_varint(fp, signed=False)
        else:


@@ 478,7 478,9 @@ class Map(Field):
            key = self._keytype._unpack(fp)
            value = self._valuetype.unpack(fp)
            values[key] = value
        return self.__class__(keytype=self._keytype, valuetype=self._valuetype, value=values)
        return self.__class__(
            keytype=self._keytype, valuetype=self._valuetype, value=values
        )

    def to_dict(self, value=None):
        if value is None:


@@ 528,19 530,20 @@ class Optional(Field):
        if value is None:
            value = self._value
        if value is None:
            fp.write(struct.pack('<B', 0))
            fp.write(struct.pack("<B", 0))
        else:
            fp.write(struct.pack('<B', 1))
            fp.write(struct.pack("<B", 1))
            self._wrapped._pack(fp, value=value)

    def _unpack(self, fp: typing.BinaryIO) -> 'Optional':
    def _unpack(self, fp: typing.BinaryIO) -> "Optional":
        buf = fp.read(1)
        check = struct.unpack('<B', buf)[0]
        check = struct.unpack("<B", buf)[0]
        if check == 0:
            return self.__class__(wrapped=self._wrapped, value=None)
        value = self._wrapped._unpack(fp)
        return self.__class__(wrapped=self._wrapped, value=value)


class Union(Field):

    _members: typing.Tuple[typing.Union[Field, Struct], ...] = ()


@@ 557,7 560,9 @@ class Union(Field):
        if value is not None:
            valid, message = self.validate(value)
            if not valid:
                raise ValidationError(f"Attempting to set incorrect value to Union type: {type(value)}")
                raise ValidationError(
                    f"Attempting to set incorrect value to Union type: {type(value)}"
                )
        self._value = value

    @property

M bare/test_encoder.py => bare/test_encoder.py +67 -24
@@ 120,9 120,13 @@ class ExampleUnion(Union):
class UnionTest(Struct):
    e = ExampleUnion()
    b = Union(members=(Str, Int))
    c = Union(members=(OptionalStruct,ArrayTest))
    c = Union(members=(OptionalStruct, ArrayTest))


def test_union():
    ex = UnionTest(e=1, b="test", c=ArrayTest(a=[1], n=[Nested(s='s')])) # MUST specify values for union types when creating an object
    ex = UnionTest(
        e=1, b="test", c=ArrayTest(a=[1], n=[Nested(s="s")])
    )  # MUST specify values for union types when creating an object
    assert ex.e == 1
    ex.e = "1"
    assert ex.e == "1"


@@ 133,16 137,19 @@ def test_union():
    assert ex.e == ex.e
    assert ex.b == ex.b
    assert ex.c.a == [1]
    assert ex.c.n[0].s == 's'
    assert ex.c.n[0].s == "s"
    assert ex.c.a == [1]


class EnumTest(enum.Enum):
    TEST = 0
    TEST2 = 1


class EnumTestStruct(Struct):
    e = Enum(EnumTest)


def test_enum():
    ex = EnumTestStruct(e=0)
    assert ex.e == 0


@@ 153,9 160,11 @@ def test_enum():
class PublicKey(DataFixed):
    _length = 128


class Time(Str):
    pass


class Department(enum.Enum):
    ACCOUNTING = 0
    ADMINISTRATION = 1


@@ 164,16 173,19 @@ class Department(enum.Enum):

    JSMITH = 99


class Address(Struct):
    address = Array(Str, length=4)
    city = Str()
    state = Str()
    country = Str()


class Order(Struct):
    orderID = I64()
    quantity = I32()


class Customer(Struct):
    name = Str()
    email = Str()


@@ 181,6 193,7 @@ class Customer(Struct):
    orders = Array(Order)
    metadata = Map(Str, Data)


class Employee(Struct):
    name = Str()
    email = Str()


@@ 190,15 203,20 @@ class Employee(Struct):
    publicKey = Optional(PublicKey)
    metadata = Map(Str, Data)


class TerminatedEmployee(Void):
    pass


class Person(Union):
    _members = (Customer, Employee, TerminatedEmployee)

@pytest.mark.parametrize('file', ['customer.bin', 'employee.bin', 'people.bin', 'terminated.bin'])

@pytest.mark.parametrize(
    "file", ["customer.bin", "employee.bin", "people.bin", "terminated.bin"]
)
def test_people(file):
    with open(os.path.join(os.path.dirname(__file__), '_examples', file), 'br') as f:
    with open(os.path.join(os.path.dirname(__file__), "_examples", file), "br") as f:
        people = []
        while True:
            try:


@@ 213,42 231,62 @@ def test_people(file):
            person.pack(buf)
        assert buf.getvalue() == f


def test_varint():
    expected = b'\x18'
    expected = b"\x18"
    i = Int(value=12)
    assert i.pack() == expected

    i = Int(value=12345)
    expected = b'\xf2\xc0\x01'
    expected = b"\xf2\xc0\x01"
    assert i.pack() == expected
    i = Int(value=-12345678)
    expected = b'\x9b\x85\xe3\x0b'
    expected = b"\x9b\x85\xe3\x0b"
    assert i.pack() == expected


def test_uvarint():
    expected = b'\xce\xc2\xf1\x05'
    expected = b"\xce\xc2\xf1\x05"
    i = UInt(value=12345678)
    assert i.pack() == expected


def test_string():
    expected = b'\x0d\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67'
    expected = b"\x0d\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67"
    s = Str(value="a test string")
    assert s.pack() == expected
    s = Str(value="")
    assert s.pack() == b'\x00'

@pytest.mark.parametrize('value', [
    (Str(value='a test string'),b'\x0d\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67\x0d\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67\x0d\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67\x0d\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67'),
    (Int(value=12345678), b'\x9c\x85\xe3\x0b\x9c\x85\xe3\x0b\x9c\x85\xe3\x0b\x9c\x85\xe3\x0b')
])
    assert s.pack() == b"\x00"


@pytest.mark.parametrize(
    "value",
    [
        (
            Str(value="a test string"),
            b"\x0d\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67\x0d\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67\x0d\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67\x0d\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67",
        ),
        (
            Int(value=12345678),
            b"\x9c\x85\xe3\x0b\x9c\x85\xe3\x0b\x9c\x85\xe3\x0b\x9c\x85\xe3\x0b",
        ),
    ],
)
def test_fixed_array(value):
    a = Array(type=value[0].__class__, length=4, values=[value[0]] * 4)
    assert a.pack() == value[1]

@pytest.mark.parametrize('value',[
    (Str(value='a test string'),b'\x04\x0d\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67\x0d\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67\x0d\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67\x0d\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67'),
    (Int(value=123456),b'\x04\x80\x89\x0f\x80\x89\x0f\x80\x89\x0f\x80\x89\x0f')
])

@pytest.mark.parametrize(
    "value",
    [
        (
            Str(value="a test string"),
            b"\x04\x0d\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67\x0d\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67\x0d\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67\x0d\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67",
        ),
        (Int(value=123456), b"\x04\x80\x89\x0f\x80\x89\x0f\x80\x89\x0f\x80\x89\x0f"),
    ],
)
def test_array(value):
    a = Array(type=value[0].__class__, values=[value[0]] * 4)
    packed = a.pack()


@@ 257,21 295,26 @@ def test_array(value):
    unpacked = a.unpack(buf)
    assert unpacked.to_dict() == a.to_dict()


class B(Struct):
    c = Int()


class X(Struct):
    a = Str()
    b = B()


def test_struct():
    s = X(a='a test string', b=B(c=12345))
    expected = b'\x0d\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67\xf2\xc0\x01'
    s = X(a="a test string", b=B(c=12345))
    expected = b"\x0d\x61\x20\x74\x65\x73\x74\x20\x73\x74\x72\x69\x6e\x67\xf2\xc0\x01"
    assert s.pack() == expected
    buf = io.BytesIO(expected)
    unpacked = s.unpack(buf)
    assert unpacked.to_dict() == s.to_dict()


def test_map():
    expected = b'\x02\x04\x74\x65\x73\x74\x04\x74\x65\x73\x74\x07\x61\x6e\x6f\x74\x68\x65\x72\x04\x63\x61\x73\x65'
    m = Map(Str, Str, value={'test': 'test', 'another': 'case'})
    expected = b"\x02\x04\x74\x65\x73\x74\x04\x74\x65\x73\x74\x07\x61\x6e\x6f\x74\x68\x65\x72\x04\x63\x61\x73\x65"
    m = Map(Str, Str, value={"test": "test", "another": "case"})
    assert m.pack() == expected

M bare/types.py => bare/types.py +15 -5
@@ 239,6 239,7 @@ class Int(Field):
        val = _read_varint(fp, signed=True)
        return self.__class__(value=val)


class UInt(Field):

    _type = BareType.UINT


@@ 248,7 249,10 @@ class UInt(Field):
        if not isinstance(value, int):
            return False, f"type: {type(value)} must be <int>"
        if value < 0:
            return False, f"value: {value} is outside of valid range for this type: {self.__class__._type}",
            return (
                False,
                f"value: {value} is outside of valid range for this type: {self.__class__._type}",
            )
        return True, None

    def _pack(self, fp: typing.BinaryIO, value=None):


@@ 344,8 348,8 @@ class DataFixed(Field):
        val = struct.unpack(f"<{length}s", fp)[0]
        return self.__class__(value=val)

class Enum(UInt):

class Enum(UInt):
    def __init__(self, enum, *args, **kwargs):
        self._enum = enum
        super().__init__(*args, **kwargs)


@@ 354,12 358,18 @@ class Enum(UInt):
        if not isinstance(value, int):
            return False, f"type: {type(value)} is not valid for Enum, must be <int>"
        if value < 0:
            return False, f"value is not a valid value for Enum {self.__class__.__name__}"
            return (
                False,
                f"value is not a valid value for Enum {self.__class__.__name__}",
            )
        values = set(item.value for item in self._enum.__members__.values())
        if value not in values:
            return False, f"value {value} is not a valid Enum type for {self.__class__.__name__}"
            return (
                False,
                f"value {value} is not a valid Enum type for {self.__class__.__name__}",
            )
        return True, None

    def _unpack(self, fp: typing.BinaryIO) -> 'UInt':
    def _unpack(self, fp: typing.BinaryIO) -> "UInt":
        val = _read_varint(fp, signed=False)
        return self.__class__(self._enum, val)