pybare 1.2.4__tar.gz → 1.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. {pybare-1.2.4 → pybare-1.3.0}/PKG-INFO +22 -19
  2. {pybare-1.2.4 → pybare-1.3.0}/README.md +2 -0
  3. {pybare-1.2.4 → pybare-1.3.0}/bare/barestruct.py +19 -7
  4. {pybare-1.2.4 → pybare-1.3.0}/bare/baretype.py +6 -3
  5. {pybare-1.2.4 → pybare-1.3.0}/bare/data.py +2 -0
  6. {pybare-1.2.4 → pybare-1.3.0}/bare/number.py +31 -21
  7. {pybare-1.2.4 → pybare-1.3.0}/bare/test_barearray.py +8 -3
  8. pybare-1.3.0/bare/test_barestruct.py +108 -0
  9. {pybare-1.2.4 → pybare-1.3.0}/bare/test_data.py +21 -3
  10. {pybare-1.2.4 → pybare-1.3.0}/bare/test_encoder.py +12 -9
  11. {pybare-1.2.4 → pybare-1.3.0}/bare/test_map.py +6 -2
  12. {pybare-1.2.4 → pybare-1.3.0}/bare/test_misc.py +4 -1
  13. pybare-1.3.0/bare/test_number.py +93 -0
  14. {pybare-1.2.4 → pybare-1.3.0}/bare/test_union.py +10 -4
  15. {pybare-1.2.4 → pybare-1.3.0}/bare/union.py +1 -0
  16. pybare-1.3.0/bare/util.py +65 -0
  17. {pybare-1.2.4 → pybare-1.3.0}/pyproject.toml +1 -1
  18. pybare-1.2.4/bare/test_barestruct.py +0 -35
  19. pybare-1.2.4/bare/test_number.py +0 -49
  20. pybare-1.2.4/bare/util.py +0 -42
  21. {pybare-1.2.4 → pybare-1.3.0}/LICENSE +0 -0
  22. {pybare-1.2.4 → pybare-1.3.0}/bare/__init__.py +0 -0
  23. {pybare-1.2.4 → pybare-1.3.0}/bare/_examples/customer.bin +0 -0
  24. {pybare-1.2.4 → pybare-1.3.0}/bare/_examples/employee.bin +0 -0
  25. {pybare-1.2.4 → pybare-1.3.0}/bare/_examples/people.bin +0 -0
  26. {pybare-1.2.4 → pybare-1.3.0}/bare/_examples/terminated.bin +0 -0
  27. {pybare-1.2.4 → pybare-1.3.0}/bare/barearray.py +0 -0
  28. {pybare-1.2.4 → pybare-1.3.0}/bare/map.py +0 -0
  29. {pybare-1.2.4 → pybare-1.3.0}/bare/misc.py +0 -0
@@ -1,27 +1,28 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: pybare
3
- Version: 1.2.4
3
+ Version: 1.3.0
4
4
  Summary: A BARE Encoding Library and Data Validation Library for Python.
5
5
  Author-Email: Noah Pederson <noah@packetlost.dev>
6
6
  License: Copyright 2020 Noah Pederson
7
-
8
- Permission is hereby granted, free of charge, to any person obtaining a copy of
9
- this software and associated documentation files (the "Software"), to deal in
10
- the Software without restriction, including without limitation the rights to
11
- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12
- of the Software, and to permit persons to whom the Software is furnished to do
13
- so, subject to the following conditions:
14
-
15
- The above copyright notice and this permission notice shall be included in all
16
- copies or substantial portions of the Software.
17
-
18
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
- SOFTWARE.
7
+
8
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
9
+ this software and associated documentation files (the "Software"), to deal in
10
+ the Software without restriction, including without limitation the rights to
11
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
12
+ of the Software, and to permit persons to whom the Software is furnished to do
13
+ so, subject to the following conditions:
14
+
15
+ The above copyright notice and this permission notice shall be included in all
16
+ copies or substantial portions of the Software.
17
+
18
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24
+ SOFTWARE.
25
+
25
26
  Classifier: Development Status :: 4 - Beta
26
27
  Classifier: Intended Audience :: Developers
27
28
  Classifier: Programming Language :: Python :: 3.10
@@ -38,6 +39,8 @@ Description-Content-Type: text/markdown
38
39
 
39
40
  # PyBARE
40
41
  [![builds.sr.ht status](https://builds.sr.ht/~chiefnoah/pybare.svg)](https://builds.sr.ht/~chiefnoah/pybare?)
42
+ [![Static
43
+ Badge](https://img.shields.io/badge/Docs-green?style=flat&logo=python)](https://pybare.ngp.computer)
41
44
 
42
45
  A declarative implementation of the [BARE](https://baremessages.org/) message
43
46
  format for Python 3.10+
@@ -1,5 +1,7 @@
1
1
  # PyBARE
2
2
  [![builds.sr.ht status](https://builds.sr.ht/~chiefnoah/pybare.svg)](https://builds.sr.ht/~chiefnoah/pybare?)
3
+ [![Static
4
+ Badge](https://img.shields.io/badge/Docs-green?style=flat&logo=python)](https://pybare.ngp.computer)
3
5
 
4
6
  A declarative implementation of the [BARE](https://baremessages.org/) message
5
7
  format for Python 3.10+
@@ -1,7 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import io
4
- from typing import Any, BinaryIO, Dict, Type, TypeVar, _ProtocolMeta
4
+ from typing import Any, BinaryIO, Type, TypeVar, _ProtocolMeta
5
5
 
6
6
  from .baretype import BAREType
7
7
  from .util import Field
@@ -67,14 +67,26 @@ class Struct(BAREType, metaclass=StructMeta):
67
67
 
68
68
  def __init__(self, **kwargs):
69
69
  name = self.__class__.__name__
70
- for key, value in kwargs.items():
70
+ for key in kwargs:
71
71
  if key not in self._fields:
72
72
  raise ValueError(f"Got unexpected field for Struct {name}: {key}")
73
- if not isinstance(value, self._fields[key]):
74
- value = self._fields[key](value)
73
+
74
+ missing_fields = []
75
+ for key, ty in self._fields.items():
76
+ field = self.__class__.__dict__[key]
77
+ if key in kwargs:
78
+ value = kwargs[key]
79
+ elif field.has_default():
80
+ value = field.get_default()
81
+ elif getattr(ty, "_optional", False):
82
+ value = None
83
+ else:
84
+ missing_fields.append(key)
85
+ continue
86
+ if not isinstance(value, ty):
87
+ value = ty(value)
75
88
  setattr(self, key, value)
76
- missing_fields = set(kwargs.keys()) - set(self._fields.keys())
77
- if len(missing_fields) > 0:
89
+ if missing_fields:
78
90
  raise ValueError(
79
91
  f"Missing fields for {name}.__init__. "
80
92
  f"Expected: {', '.join(missing_fields)}"
@@ -122,7 +134,7 @@ class Struct(BAREType, metaclass=StructMeta):
122
134
  return NotImplemented
123
135
 
124
136
 
125
- def struct(**kwargs: Dict[str, Type[BAREType]]) -> Type[Struct]:
137
+ def struct(**kwargs: Type[BAREType]) -> Type[Struct]:
126
138
  """
127
139
  A function that defines and returnes an anonymous BARE `Struct` subclass with the
128
140
  provided `kwargs` as fields. The name of each kwarg becomes the field name, with the
@@ -7,14 +7,17 @@ T = TypeVar("T", covariant=True)
7
7
 
8
8
  class BAREType(Protocol, Generic[T]):
9
9
  @abstractmethod
10
- def __init__(self, *arg, **kwargs): ...
10
+ def __init__(self, *arg, **kwargs):
11
+ ...
11
12
 
12
13
  @abstractmethod
13
- def pack(self) -> bytes: ...
14
+ def pack(self) -> bytes:
15
+ ...
14
16
 
15
17
  @classmethod
16
18
  @abstractmethod
17
- def unpack(cls, fp: BinaryIO) -> BAREType: ...
19
+ def unpack(cls, fp: BinaryIO) -> BAREType:
20
+ ...
18
21
 
19
22
  @classmethod
20
23
  @abstractmethod
@@ -68,6 +68,8 @@ class Data(BAREType[bytes], metaclass=DataMeta):
68
68
  def unpack(cls, fp: BinaryIO) -> Data:
69
69
  size = cls._size or UInt.unpack(fp).value
70
70
  buf = fp.read(size)
71
+ if len(buf) != size:
72
+ raise EOFError(f"Expected {size} data bytes, got {len(buf)}")
71
73
  return cls(buf)
72
74
 
73
75
 
@@ -113,7 +113,7 @@ class Int(NumberMixin[int]):
113
113
  return True
114
114
  try:
115
115
  value = int(value)
116
- return -(1 << 63) <= value and value <= (1 << 63)
116
+ return -(1 << 63) <= value and value < (1 << 63)
117
117
  except (ValueError, TypeError):
118
118
  return False
119
119
 
@@ -139,7 +139,7 @@ class F64(NumberMixin[float]):
139
139
 
140
140
  @classmethod
141
141
  def unpack(cls, fp: BinaryIO) -> F64:
142
- buf = fp.read(struct.calcsize("<d"))
142
+ buf = _read_exact(fp, struct.calcsize("<d"))
143
143
  return cls(struct.unpack("<d", buf)[0])
144
144
 
145
145
  @classmethod
@@ -172,7 +172,7 @@ class F32(NumberMixin[float]):
172
172
 
173
173
  @classmethod
174
174
  def unpack(cls, fp: BinaryIO) -> F32:
175
- buf = fp.read(struct.calcsize("<f"))
175
+ buf = _read_exact(fp, struct.calcsize("<f"))
176
176
  return cls(struct.unpack("<f", buf)[0]) # type: ignore
177
177
 
178
178
  @classmethod
@@ -196,7 +196,7 @@ class U8(NumberMixin[int]):
196
196
 
197
197
  @classmethod
198
198
  def unpack(cls, fp: BinaryIO) -> U8:
199
- buf = fp.read(struct.calcsize("<B"))
199
+ buf = _read_exact(fp, struct.calcsize("<B"))
200
200
  return cls(struct.unpack("<B", buf)[0]) # type: ignore
201
201
 
202
202
  @classmethod
@@ -220,7 +220,7 @@ class Bool(NumberMixin[bool]):
220
220
 
221
221
  @classmethod
222
222
  def unpack(cls, fp: BinaryIO) -> Bool:
223
- buf = fp.read(struct.calcsize("<?"))
223
+ buf = _read_exact(fp, struct.calcsize("<?"))
224
224
  return cls(struct.unpack("<?", buf)[0]) # type: ignore
225
225
 
226
226
  @classmethod
@@ -238,7 +238,7 @@ class I8(NumberMixin[int]):
238
238
 
239
239
  @classmethod
240
240
  def unpack(cls, fp: BinaryIO) -> I8:
241
- buf = fp.read(struct.calcsize("<b"))
241
+ buf = _read_exact(fp, struct.calcsize("<b"))
242
242
  return cls(struct.unpack("<b", buf)[0]) # type: ignore
243
243
 
244
244
  @classmethod
@@ -262,7 +262,7 @@ class U16(NumberMixin[int]):
262
262
 
263
263
  @classmethod
264
264
  def unpack(cls, fp: BinaryIO) -> U16:
265
- buf = fp.read(struct.calcsize("<H"))
265
+ buf = _read_exact(fp, struct.calcsize("<H"))
266
266
  return cls(struct.unpack("<H", buf)[0]) # type: ignore
267
267
 
268
268
  @classmethod
@@ -286,7 +286,7 @@ class I16(NumberMixin[int]):
286
286
 
287
287
  @classmethod
288
288
  def unpack(cls, fp: BinaryIO) -> I16:
289
- buf = fp.read(struct.calcsize("<h"))
289
+ buf = _read_exact(fp, struct.calcsize("<h"))
290
290
  return cls(struct.unpack("<h", buf)[0]) # type: ignore
291
291
 
292
292
  @classmethod
@@ -310,7 +310,7 @@ class U32(NumberMixin[int]):
310
310
 
311
311
  @classmethod
312
312
  def unpack(cls, fp: BinaryIO) -> U32:
313
- buf = fp.read(struct.calcsize("<L"))
313
+ buf = _read_exact(fp, struct.calcsize("<L"))
314
314
  return cls(struct.unpack("<L", buf)[0]) # type: ignore
315
315
 
316
316
  @classmethod
@@ -334,7 +334,7 @@ class I32(NumberMixin[int]):
334
334
 
335
335
  @classmethod
336
336
  def unpack(cls, fp: BinaryIO) -> I32:
337
- buf = fp.read(struct.calcsize("<l"))
337
+ buf = _read_exact(fp, struct.calcsize("<l"))
338
338
  return cls(struct.unpack("<l", buf)[0]) # type: ignore
339
339
 
340
340
  @classmethod
@@ -358,7 +358,7 @@ class U64(NumberMixin[int]):
358
358
 
359
359
  @classmethod
360
360
  def unpack(cls, fp: BinaryIO) -> U64:
361
- buf = fp.read(struct.calcsize("<Q"))
361
+ buf = _read_exact(fp, struct.calcsize("<Q"))
362
362
  return cls(struct.unpack("<Q", buf)[0]) # type: ignore
363
363
 
364
364
  @classmethod
@@ -385,7 +385,7 @@ class I64(NumberMixin[int]):
385
385
 
386
386
  @classmethod
387
387
  def unpack(cls, fp: BinaryIO) -> I64:
388
- buf = fp.read(struct.calcsize("<q"))
388
+ buf = _read_exact(fp, struct.calcsize("<q"))
389
389
  return cls(struct.unpack("<q", buf)[0]) # type: ignore
390
390
 
391
391
  @classmethod
@@ -399,6 +399,13 @@ class I64(NumberMixin[int]):
399
399
  return False
400
400
 
401
401
 
402
+ def _read_exact(fp: BinaryIO, size: int) -> bytes:
403
+ buf = fp.read(size)
404
+ if len(buf) != size:
405
+ raise EOFError(f"Expected {size} bytes, got {len(buf)}")
406
+ return buf
407
+
408
+
402
409
  # This is adapted from https://git.sr.ht/~martijnbraam/bare-py/tree/master/bare/__init__.py#L29
403
410
  def _write_varint(fp: BinaryIO, val: int, signed=True) -> int:
404
411
  written = 0
@@ -416,19 +423,22 @@ def _write_varint(fp: BinaryIO, val: int, signed=True) -> int:
416
423
 
417
424
  def _read_varint(fp: BinaryIO, signed=True) -> int:
418
425
  output = 0
419
- offset = 0
420
- while True:
421
- try:
422
- b = fp.read(1)[0]
423
- except IndexError:
424
- raise RuntimeError("Not enough bytes in buffer to decode")
426
+ for index in range(10):
427
+ buf = fp.read(1)
428
+ if not buf:
429
+ raise EOFError("Not enough bytes in buffer to decode varint")
430
+ b = buf[0]
431
+ if index == 9 and b > 1:
432
+ raise ValueError("Varint exceeds 64 bits")
425
433
  if b < 0x80:
426
- value = output | b << offset
434
+ if index > 0 and b == 0:
435
+ raise ValueError("Varint is not canonically encoded")
436
+ value = output | b << (index * 7)
427
437
  if signed:
428
438
  sign = value % 2
429
439
  value = value // 2
430
440
  if sign:
431
441
  value = -(value + 1)
432
442
  return value
433
- output |= (b & 0x7F) << offset
434
- offset += 7
443
+ output |= (b & 0x7F) << (index * 7)
444
+ raise ValueError("Varint exceeds 64 bits")
@@ -5,11 +5,15 @@ import bare
5
5
  import pytest
6
6
  import io
7
7
 
8
+ __all__ = []
9
+
8
10
 
9
11
  def test_2d_array_unsized():
10
- class InnerArray(Array, inner=UInt): ...
12
+ class InnerArray(Array, inner=UInt):
13
+ ...
11
14
 
12
- class My2DArray(Array, inner=InnerArray): ...
15
+ class My2DArray(Array, inner=InnerArray):
16
+ ...
13
17
 
14
18
  input = [[1, 2], [3, 4]]
15
19
  x = My2DArray(input)
@@ -45,7 +49,8 @@ def test_anonymous_array_sized():
45
49
 
46
50
 
47
51
  def test_array_sized():
48
- class MyArry(Array, inner=UInt, size=3): ...
52
+ class MyArry(Array, inner=UInt, size=3):
53
+ ...
49
54
 
50
55
  input = [1, 2, 3]
51
56
  x = MyArry(input)
@@ -0,0 +1,108 @@
1
+ import io
2
+ from bare import Struct, UInt, Str, Void, Field, array, optional, struct
3
+ import pytest
4
+
5
+ __all__ = []
6
+
7
+
8
+ def test_struct_basic():
9
+ class MyStruct(Struct):
10
+ a = Field(UInt)
11
+ b = Field(Str)
12
+ c: int
13
+
14
+ x = MyStruct(a=123, b="Hello")
15
+ b = x.pack()
16
+ x2 = MyStruct.unpack(io.BytesIO(b))
17
+ assert x == x2
18
+
19
+
20
+ def test_struct_requires_all_fields():
21
+ class MyStruct(Struct):
22
+ a = Field(UInt)
23
+ b = Field(Str)
24
+
25
+ with pytest.raises(ValueError, match="Missing fields"):
26
+ MyStruct(a=1)
27
+
28
+ with pytest.raises(ValueError, match="Missing fields"):
29
+ MyStruct()
30
+
31
+
32
+ def test_struct_field_default():
33
+ class MyStruct(Struct):
34
+ name = Field(Str)
35
+ count = Field(UInt, default=0)
36
+
37
+ value = MyStruct(name="example")
38
+
39
+ assert value.count == 0
40
+ assert MyStruct.unpack(io.BytesIO(value.pack())) == value
41
+
42
+
43
+ def test_struct_field_default_factory_is_fresh_per_instance():
44
+ Values = array(UInt)
45
+
46
+ class MyStruct(Struct):
47
+ values = Field(Values, default_factory=list)
48
+
49
+ first = MyStruct()
50
+ second = MyStruct()
51
+ first.values.append(1)
52
+
53
+ assert first.values == [1]
54
+ assert second.values == []
55
+
56
+
57
+ def test_struct_field_default_is_validated():
58
+ class MyStruct(Struct):
59
+ count = Field(UInt, default=-1)
60
+
61
+ with pytest.raises(TypeError):
62
+ MyStruct()
63
+
64
+
65
+ def test_field_rejects_default_and_default_factory():
66
+ with pytest.raises(TypeError, match="both default and default_factory"):
67
+ Field(UInt, default=0, default_factory=lambda: 0)
68
+
69
+
70
+ def test_optional_struct_field_defaults_to_void():
71
+ class MyStruct(Struct):
72
+ name = Field(Str)
73
+ description = Field(optional(Str))
74
+
75
+ value = MyStruct(name="example")
76
+
77
+ assert value.description == Void()
78
+ assert MyStruct.unpack(io.BytesIO(value.pack())) == value
79
+
80
+
81
+ def test_explicit_optional_struct_field_overrides_implicit_default():
82
+ class MyStruct(Struct):
83
+ description = Field(optional(Str))
84
+
85
+ value = MyStruct(description=Str("provided"))
86
+
87
+ assert value.description == "provided"
88
+
89
+
90
+ def test_nested_struct():
91
+ class InnerStruct(Struct):
92
+ i = Field(UInt)
93
+
94
+ class MyStruct(Struct):
95
+ inner = Field(InnerStruct)
96
+
97
+ x = MyStruct(inner=InnerStruct(i=123))
98
+ b = x.pack()
99
+ x2 = MyStruct.unpack(io.BytesIO(b))
100
+ assert x == x2
101
+
102
+
103
+ def test_anonymous_struct():
104
+ MyStruct = struct(name=Str, age=UInt)
105
+ x = MyStruct(name="Noah", age=27)
106
+ b = x.pack()
107
+ x2 = MyStruct.unpack(io.BytesIO(b))
108
+ assert x == x2
@@ -1,9 +1,13 @@
1
- from bare import Data, data
1
+ from bare import Data
2
2
  import io
3
+ import pytest
4
+
5
+ __all__ = []
3
6
 
4
7
 
5
8
  def test_data_sized():
6
- class SizedData(Data, size=5): ...
9
+ class SizedData(Data, size=5):
10
+ ...
7
11
 
8
12
  x = SizedData(b"12345")
9
13
  b = x.pack()
@@ -13,7 +17,8 @@ def test_data_sized():
13
17
 
14
18
 
15
19
  def test_data_unsized():
16
- class SizedData(Data): ...
20
+ class SizedData(Data):
21
+ ...
17
22
 
18
23
  x = SizedData(b"12345")
19
24
  b = x.pack()
@@ -28,3 +33,16 @@ def test_data_raw():
28
33
  assert len(b) == 6
29
34
  x2 = Data.unpack(io.BytesIO(b))
30
35
  assert x == x2
36
+
37
+
38
+ def test_data_rejects_truncated_input():
39
+ with pytest.raises(EOFError):
40
+ Data.unpack(io.BytesIO(b"\x05abc"))
41
+
42
+
43
+ def test_sized_data_rejects_truncated_input():
44
+ class SizedData(Data, size=5):
45
+ ...
46
+
47
+ with pytest.raises(EOFError):
48
+ SizedData.unpack(io.BytesIO(b"abc"))
@@ -27,6 +27,8 @@ from bare import (
27
27
  UnionVariant,
28
28
  )
29
29
 
30
+ __all__ = []
31
+
30
32
 
31
33
  class Nested(Struct):
32
34
  s = Field(Str)
@@ -42,7 +44,7 @@ class Example(Struct):
42
44
 
43
45
  def test_example_struct():
44
46
  n = Nested(s="nested")
45
- ex = Example(testint=11, teststr="a test", n=n)
47
+ ex = Example(testint=11, teststr="a test", testuint=0, n=n)
46
48
  assert hasattr(ex, "testint")
47
49
  assert hasattr(ex, "teststr")
48
50
  assert hasattr(ex, "n")
@@ -50,7 +52,7 @@ def test_example_struct():
50
52
  assert ex.testint == 11
51
53
  assert ex.teststr == "a test"
52
54
  assert ex.n.s == "nested"
53
- ex2 = Example(testint=12, teststr="another test")
55
+ ex2 = Example(testint=12, teststr="another test", testuint=0, n=n)
54
56
  assert ex2.testint == 12
55
57
  assert ex2.teststr == "another test"
56
58
  # Check that the values in the original instance haven't been modified
@@ -100,7 +102,8 @@ def test_map():
100
102
  assert map2.value["another"] == "test"
101
103
 
102
104
 
103
- class MyArray(Array, inner=Int): ...
105
+ class MyArray(Array, inner=Int):
106
+ ...
104
107
 
105
108
 
106
109
  class ArrayTest(Struct):
@@ -109,9 +112,7 @@ class ArrayTest(Struct):
109
112
 
110
113
 
111
114
  def test_array_struct():
112
- ex = ArrayTest()
113
- ex.a = [1, 2, 3]
114
- ex.n = [Nested(s="test")]
115
+ ex = ArrayTest(a=[1, 2, 3], n=[Nested(s="test")])
115
116
  assert ex.a == [1, 2, 3]
116
117
  with pytest.raises(TypeError):
117
118
  ex.a = ["a", "b", "c"]
@@ -141,7 +142,8 @@ def test_optional():
141
142
  ex.nested = "test"
142
143
 
143
144
 
144
- class ExampleUnion(Union, variants=(Str, Int)): ...
145
+ class ExampleUnion(Union, variants=(Str, Int)):
146
+ ...
145
147
 
146
148
 
147
149
  class UnionTest(Struct):
@@ -234,7 +236,8 @@ class TerminatedEmployee(Void):
234
236
  pass
235
237
 
236
238
 
237
- class Person(Union, variants=(Customer, Employee, TerminatedEmployee)): ...
239
+ class Person(Union, variants=(Customer, Employee, TerminatedEmployee)):
240
+ ...
238
241
 
239
242
 
240
243
  @pytest.mark.parametrize(
@@ -247,7 +250,7 @@ def test_people(file):
247
250
  try:
248
251
  p = Person.unpack(f)
249
252
  people.append(p)
250
- except RuntimeError:
253
+ except EOFError:
251
254
  break
252
255
  f.seek(0)
253
256
  f = f.read()
@@ -4,8 +4,11 @@ import io
4
4
 
5
5
  import pytest
6
6
 
7
+ __all__ = []
7
8
 
8
- class MyMap(Map, key_type=Str, value_type=Int): ...
9
+
10
+ class MyMap(Map, key_type=Str, value_type=Int):
11
+ ...
9
12
 
10
13
 
11
14
  def test_basic_map():
@@ -30,7 +33,8 @@ def test_anonymous_map():
30
33
  def test_nested_map():
31
34
  InnerMap = map(Str, Str)
32
35
 
33
- class NestedMap(Map, key_type=Str, value_type=InnerMap): ...
36
+ class NestedMap(Map, key_type=Str, value_type=InnerMap):
37
+ ...
34
38
 
35
39
  x = NestedMap({"outer": {"hello": "world"}})
36
40
  b = x.pack()
@@ -3,6 +3,8 @@ from bare import Enum, UInt, Void, Str
3
3
  import io
4
4
  import pytest
5
5
 
6
+ __all__ = []
7
+
6
8
 
7
9
  class MyEnum(Enum):
8
10
  A = 1
@@ -19,7 +21,8 @@ def test_enum_basic():
19
21
  MyEnum.unpack(io.BytesIO(b))
20
22
 
21
23
 
22
- class MyVoid(Void): ...
24
+ class MyVoid(Void):
25
+ ...
23
26
 
24
27
 
25
28
  def test_custom_void():
@@ -0,0 +1,93 @@
1
+ import io
2
+ import bare
3
+ import pytest
4
+
5
+ __all__ = []
6
+
7
+
8
+ @pytest.mark.parametrize(
9
+ "type_, good_values, bad_values",
10
+ [
11
+ (bare.UInt, [0, 1, 2**64 - 1], [-1, 2**64, 2**64 + 1]),
12
+ (
13
+ bare.Int,
14
+ [-(2**63), -(2**63) + 1, 0, 2**63 - 1],
15
+ [-(2**63) - 1, 2**63, 2**63 + 1],
16
+ ),
17
+ (bare.U8, [0, 1, 2**8 - 1], [-1, 2**8]),
18
+ (bare.I8, [-(2**7), 0, 2**7 - 1], [-(2**7) - 1, 2**7]),
19
+ (bare.U16, [0, 1, 2**16 - 1], [-1, 2**16]),
20
+ (bare.I16, [-(2**15), 0, 2**15 - 1], [-(2**15) - 1, 2**15]),
21
+ (bare.U32, [0, 1, 2**32 - 1], [-1, 2**32]),
22
+ (bare.I32, [-(2**31), 0, 2**31 - 1], [-(2**31) - 1, 2**31]),
23
+ (bare.U64, [0, 1, 2**64 - 1], [-1, 2**64]),
24
+ (bare.I64, [-(2**63), 0, 2**63 - 1], [-(2**63) - 1, 2**63]),
25
+ ],
26
+ )
27
+ def test_bounds(type_, good_values, bad_values):
28
+ for value in good_values:
29
+ x = type_(value)
30
+ encoded = x.pack()
31
+ decoded = type_.unpack(io.BytesIO(encoded))
32
+ assert decoded == x
33
+ assert decoded.value == value
34
+ for value in bad_values:
35
+ with pytest.raises(TypeError):
36
+ type_(value)
37
+
38
+
39
+ @pytest.mark.parametrize("value", [-(2**63), -(2**63) + 1, 0, 2**63 - 1])
40
+ def test_int_boundary_values_roundtrip(value):
41
+ encoded = bare.Int(value).pack()
42
+ decoded = bare.Int.unpack(io.BytesIO(encoded))
43
+
44
+ assert decoded.value == value
45
+ assert decoded.pack() == encoded
46
+
47
+
48
+ @pytest.mark.parametrize("value", [-(2**63) - 1, 2**63])
49
+ def test_int_rejects_values_that_cannot_roundtrip(value):
50
+ with pytest.raises(TypeError):
51
+ bare.Int(value).pack()
52
+
53
+
54
+ def test_f32():
55
+ x = bare.F32(123.5)
56
+ b = x.pack()
57
+ assert len(b) == 4
58
+ x2 = bare.F32.unpack(io.BytesIO(b))
59
+ assert x == x2
60
+
61
+
62
+ def test_f64():
63
+ x = bare.F64(123.5)
64
+ b = x.pack()
65
+ assert len(b) == 8
66
+ x2 = bare.F64.unpack(io.BytesIO(b))
67
+ assert x == x2
68
+
69
+
70
+ @pytest.mark.parametrize("encoded", [b"\x80\x00", b"\x80" * 10 + b"\x00"])
71
+ def test_uint_rejects_noncanonical_or_oversized_varints(encoded):
72
+ with pytest.raises(ValueError):
73
+ bare.UInt.unpack(io.BytesIO(encoded))
74
+
75
+
76
+ @pytest.mark.parametrize(
77
+ "type_,size",
78
+ [
79
+ (bare.F32, 4),
80
+ (bare.F64, 8),
81
+ (bare.U8, 1),
82
+ (bare.I8, 1),
83
+ (bare.U16, 2),
84
+ (bare.I16, 2),
85
+ (bare.U32, 4),
86
+ (bare.I32, 4),
87
+ (bare.U64, 8),
88
+ (bare.I64, 8),
89
+ ],
90
+ )
91
+ def test_fixed_width_number_rejects_truncated_input(type_, size):
92
+ with pytest.raises(EOFError):
93
+ type_.unpack(io.BytesIO(b"\x00" * (size - 1)))
@@ -3,8 +3,11 @@ import bare
3
3
  import io
4
4
  import pytest
5
5
 
6
+ __all__ = []
6
7
 
7
- class MyUnion(Union, variants=(Str, Int)): ...
8
+
9
+ class MyUnion(Union, variants=(Str, Int)):
10
+ ...
8
11
 
9
12
 
10
13
  def test_union_basic():
@@ -37,7 +40,8 @@ def test_anonymous_union():
37
40
 
38
41
 
39
42
  def test_explicit_discriminant_implicit_following():
40
- class MyUnion(Union, variants=(UnionVariant(Str, 4), Int)): ...
43
+ class MyUnion(Union, variants=(UnionVariant(Str, 4), Int)):
44
+ ...
41
45
 
42
46
  x = MyUnion(Str("Hello"))
43
47
  b = x.pack()
@@ -57,7 +61,8 @@ def test_explicit_discriminant_implicit_following():
57
61
 
58
62
 
59
63
  def test_explicit_discriminant_all_cases():
60
- class MyUnion(Union, variants=(UnionVariant(Str, 4), UnionVariant(Int, 10))): ...
64
+ class MyUnion(Union, variants=(UnionVariant(Str, 4), UnionVariant(Int, 10))):
65
+ ...
61
66
 
62
67
  x = MyUnion(Str("Hello"))
63
68
  b = x.pack()
@@ -96,7 +101,8 @@ class AllPrimativeTypes(
96
101
  bare.Data,
97
102
  bare.Void,
98
103
  ),
99
- ): ...
104
+ ):
105
+ ...
100
106
 
101
107
 
102
108
  @pytest.mark.parametrize(
@@ -210,6 +210,7 @@ def optional(maybe: Type[T]) -> Type[Union]:
210
210
  maybe,
211
211
  )
212
212
  namespace = UnionMeta.__prepare__(name, (), variants=variants)
213
+ namespace["_optional"] = True
213
214
  AnonymousUnion = UnionMeta.__new__(
214
215
  UnionMeta, name, (Union,), namespace, variants=variants
215
216
  )
@@ -0,0 +1,65 @@
1
+ from typing import Any, Callable, Type, TypeVar
2
+ from .misc import Enum
3
+
4
+ from .baretype import BAREType
5
+
6
+ T = TypeVar("T", bound=BAREType)
7
+ _MISSING = object()
8
+
9
+
10
+ class Field:
11
+ """
12
+ A descriptor that assists with assigning/retrieving values from a BARE Struct.
13
+ It is reponsible for performing type validation on assignment of struct fields.
14
+
15
+ For example:
16
+
17
+ ```
18
+ class MyStruct(Struct):
19
+ a = Field(UInt)
20
+ b = Field(Str)
21
+ ```
22
+ """
23
+
24
+ attr: str
25
+ name: str
26
+ ty: Type[BAREType | Enum]
27
+
28
+ def __init__(
29
+ self,
30
+ ty: Type[BAREType | Enum],
31
+ attr: str | None = None,
32
+ *,
33
+ default: Any = _MISSING,
34
+ default_factory: Callable[[], Any] | None = None,
35
+ ):
36
+ if default is not _MISSING and default_factory is not None:
37
+ raise TypeError("Field cannot specify both default and default_factory")
38
+ if default_factory is not None and not callable(default_factory):
39
+ raise TypeError("Field default_factory must be callable")
40
+ # ignore the typing here because these will always be set when assigned to an
41
+ # object
42
+ self.attr = attr # type: ignore
43
+ self.ty = ty
44
+ self.default = default
45
+ self.default_factory = default_factory
46
+
47
+ def has_default(self) -> bool:
48
+ return self.default is not _MISSING or self.default_factory is not None
49
+
50
+ def get_default(self) -> Any:
51
+ if self.default_factory is not None:
52
+ return self.default_factory()
53
+ return self.default
54
+
55
+ def __get__(self, inst, _) -> T | Type[BAREType[T] | Enum]:
56
+ if inst is None:
57
+ return self.ty
58
+ return inst.__dict__[self.attr]
59
+
60
+ def __set__(self, inst, value: T):
61
+ if not isinstance(value, self.ty) and not self.ty.validate(value):
62
+ raise TypeError(
63
+ f"type {type(value)} is invalid for field of BARE type {self.ty}"
64
+ )
65
+ inst.__dict__[self.attr] = value
@@ -6,7 +6,7 @@ build-backend = "pdm.backend"
6
6
 
7
7
  [project]
8
8
  name = "pybare"
9
- version = "1.2.4"
9
+ version = "1.3.0"
10
10
  description = "A BARE Encoding Library and Data Validation Library for Python."
11
11
  dependencies = []
12
12
  requires-python = ">= 3.10"
@@ -1,35 +0,0 @@
1
- import io
2
- from bare import Struct, UInt, Str, Field, struct
3
-
4
-
5
- def test_struct_basic():
6
- class MyStruct(Struct):
7
- a = Field(UInt)
8
- b = Field(Str)
9
- c: int
10
-
11
- x = MyStruct(a=123, b="Hello")
12
- b = x.pack()
13
- x2 = MyStruct.unpack(io.BytesIO(b))
14
- assert x == x2
15
-
16
-
17
- def test_nested_struct():
18
- class InnerStruct(Struct):
19
- i = Field(UInt)
20
-
21
- class MyStruct(Struct):
22
- inner = Field(InnerStruct)
23
-
24
- x = MyStruct(inner=InnerStruct(i=123))
25
- b = x.pack()
26
- x2 = MyStruct.unpack(io.BytesIO(b))
27
- assert x == x2
28
-
29
-
30
- def test_anonymous_struct():
31
- MyStruct = struct(name=Str, age=UInt)
32
- x = MyStruct(name="Noah", age=27)
33
- b = x.pack()
34
- x2 = MyStruct.unpack(io.BytesIO(b))
35
- assert x == x2
@@ -1,49 +0,0 @@
1
- import io
2
- import bare
3
- import pytest
4
-
5
-
6
- @pytest.mark.parametrize(
7
- "type_, good_values, bad_values",
8
- [
9
- (bare.UInt, [0, 1, 2**64 - 1], [-1, 2**64, 2**64 + 1]),
10
- (
11
- bare.Int,
12
- [-(2**63), -(2**63) + 1, 0, 2**63, 2**63 - 1],
13
- [-(2**63) - 1, 2**63 + 1],
14
- ),
15
- (bare.U8, [0, 1, 2**8 - 1], [-1, 2**8]),
16
- (bare.I8, [-(2**7), 0, 2**7 - 1], [-(2**7) - 1, 2**7]),
17
- (bare.U16, [0, 1, 2**16 - 1], [-1, 2**16]),
18
- (bare.I16, [-(2**15), 0, 2**15 - 1], [-(2**15) - 1, 2**15]),
19
- (bare.U32, [0, 1, 2**32 - 1], [-1, 2**32]),
20
- (bare.I32, [-(2**31), 0, 2**31 - 1], [-(2**31) - 1, 2**31]),
21
- (bare.U64, [0, 1, 2**64 - 1], [-1, 2**64]),
22
- (bare.I64, [-(2**63), 0, 2**63 - 1], [-(2**63) - 1, 2**63]),
23
- ],
24
- )
25
- def test_bounds(type_, good_values, bad_values):
26
- for value in good_values:
27
- x = type_(value)
28
- b = x.pack()
29
- x2 = type_.unpack(io.BytesIO(b))
30
- assert x == x2
31
- for value in bad_values:
32
- with pytest.raises(TypeError):
33
- type_(value)
34
-
35
-
36
- def test_f32():
37
- x = bare.F32(123.5)
38
- b = x.pack()
39
- assert len(b) == 4
40
- x2 = bare.F32.unpack(io.BytesIO(b))
41
- assert x == x2
42
-
43
-
44
- def test_f64():
45
- x = bare.F64(123.5)
46
- b = x.pack()
47
- assert len(b) == 8
48
- x2 = bare.F64.unpack(io.BytesIO(b))
49
- assert x == x2
pybare-1.2.4/bare/util.py DELETED
@@ -1,42 +0,0 @@
1
- from typing import Generic, Type, TypeVar
2
-
3
- from .baretype import BAREType
4
-
5
- T = TypeVar("T", bound=BAREType)
6
-
7
-
8
- class Field:
9
- """
10
- A descriptor that assists with assigning/retrieving values from a BARE Struct.
11
- It is reponsible for performing type validation on assignment of struct fields.
12
-
13
- For example:
14
-
15
- ```
16
- class MyStruct(Struct):
17
- a = Field(UInt)
18
- b = Field(Str)
19
- ```
20
- """
21
-
22
- attr: str
23
- name: str
24
- ty: Type[BAREType]
25
-
26
- def __init__(self, ty: Type[BAREType], attr: str | None = None):
27
- # ignore the typing here because these will always be set when assigned to an
28
- # object
29
- self.attr = attr # type: ignore
30
- self.ty = ty
31
-
32
- def __get__(self, inst, _) -> T | Type[BAREType[T]]:
33
- if inst is None:
34
- return self.ty
35
- return inst.__dict__[self.attr]
36
-
37
- def __set__(self, inst, value: T):
38
- if not isinstance(value, self.ty) and not self.ty.validate(value):
39
- raise TypeError(
40
- f"type {type(value)} is invalid for field of BARE type {self.ty}"
41
- )
42
- inst.__dict__[self.attr] = value
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes