hexconv 0.3.0__tar.gz → 0.3.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hexconv
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Pythonic conversion toolkit for bytes, hex, integers, arrays, binary strings, base64, and text.
5
5
  Author: hexconv contributors
6
6
  License-Expression: MIT
@@ -24,7 +24,9 @@ License-File: LICENSE
24
24
  Provides-Extra: dev
25
25
  Requires-Dist: build; extra == "dev"
26
26
  Requires-Dist: hypothesis; extra == "dev"
27
+ Requires-Dist: mypy; extra == "dev"
27
28
  Requires-Dist: pytest; extra == "dev"
29
+ Requires-Dist: ruff; extra == "dev"
28
30
  Requires-Dist: twine; extra == "dev"
29
31
  Dynamic: license-file
30
32
 
@@ -320,6 +322,23 @@ Available format specs all work in their simplest bare form:
320
322
  - `Bits()`
321
323
  - `Auto()`
322
324
 
325
+ The short names are ergonomic aliases of the explicit long-form specs — pick
326
+ whichever reads better, they resolve to the same format:
327
+
328
+ | Short | Long form |
329
+ | --- | --- |
330
+ | `Hex` | `HexString` |
331
+ | `Binary` | `BinaryString` |
332
+ | `Base64` / `Base32` / `Base85` / `Ascii85` | `Base64String` / `Base32String` / `Base85String` / `Ascii85String` |
333
+ | `Escaped` | `EscapedString` |
334
+ | `Hexdump` | `HexDump` |
335
+ | `Struct` | `StructFormat` |
336
+ | `Bits` | `BitArray` |
337
+ | `DecimalInt` / `DecimalIntArray` | `Int` / `IntArray` |
338
+ | `LargeHexNumber` | `HexInt` |
339
+ | `ASCIIText` / `RawString` | `Text` |
340
+ | `Group` (transform) | `Chunk` |
341
+
323
342
  Add options only when you need control over formatting or parsing:
324
343
 
325
344
  - Display: `Hex(sep=" ", prefix=True, uppercase=True)`, `HexArray(width=2)`.
@@ -290,6 +290,23 @@ Available format specs all work in their simplest bare form:
290
290
  - `Bits()`
291
291
  - `Auto()`
292
292
 
293
+ The short names are ergonomic aliases of the explicit long-form specs — pick
294
+ whichever reads better, they resolve to the same format:
295
+
296
+ | Short | Long form |
297
+ | --- | --- |
298
+ | `Hex` | `HexString` |
299
+ | `Binary` | `BinaryString` |
300
+ | `Base64` / `Base32` / `Base85` / `Ascii85` | `Base64String` / `Base32String` / `Base85String` / `Ascii85String` |
301
+ | `Escaped` | `EscapedString` |
302
+ | `Hexdump` | `HexDump` |
303
+ | `Struct` | `StructFormat` |
304
+ | `Bits` | `BitArray` |
305
+ | `DecimalInt` / `DecimalIntArray` | `Int` / `IntArray` |
306
+ | `LargeHexNumber` | `HexInt` |
307
+ | `ASCIIText` / `RawString` | `Text` |
308
+ | `Group` (transform) | `Chunk` |
309
+
293
310
  Add options only when you need control over formatting or parsing:
294
311
 
295
312
  - Display: `Hex(sep=" ", prefix=True, uppercase=True)`, `HexArray(width=2)`.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "hexconv"
7
- version = "0.3.0"
7
+ version = "0.3.1"
8
8
  description = "Pythonic conversion toolkit for bytes, hex, integers, arrays, binary strings, base64, and text."
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  requires-python = ">=3.10"
@@ -32,7 +32,9 @@ classifiers = [
32
32
  dev = [
33
33
  "build",
34
34
  "hypothesis",
35
+ "mypy",
35
36
  "pytest",
37
+ "ruff",
36
38
  "twine",
37
39
  ]
38
40
 
@@ -45,3 +47,21 @@ hexconv = ["py.typed"]
45
47
  [tool.pytest.ini_options]
46
48
  testpaths = ["tests"]
47
49
  pythonpath = ["src"]
50
+
51
+ [tool.ruff]
52
+ line-length = 100
53
+ target-version = "py310"
54
+
55
+ [tool.ruff.lint]
56
+ select = ["E", "F", "I", "B", "UP"]
57
+
58
+ [tool.ruff.lint.per-file-ignores]
59
+ # Public re-exports are grouped by concept rather than sorted alphabetically.
60
+ "src/hexconv/__init__.py" = ["I001"]
61
+
62
+ [tool.mypy]
63
+ python_version = "3.10"
64
+ no_implicit_optional = true
65
+ warn_redundant_casts = true
66
+ warn_unused_ignores = true
67
+ strict_equality = true
@@ -1,52 +1,60 @@
1
1
  """Conversion toolkit for bytes, hex, integers, arrays, binary, base64, and text."""
2
2
 
3
3
  from ._core import (
4
- ASCIIText,
5
- Auto,
4
+ # Core types and errors
6
5
  AutoResult,
7
- Ascii85,
6
+ Converter,
7
+ HexConvError,
8
+ Pipeline,
9
+ Transform,
10
+ Value,
11
+ # Format specifications (explicit long-form names)
8
12
  Ascii85String,
9
- Base64String,
10
- Base64,
11
- Base32,
13
+ Auto,
12
14
  Base32String,
13
- Base85,
15
+ Base64String,
14
16
  Base85String,
15
17
  BinaryString,
16
- Binary,
17
18
  BitArray,
18
- Bits,
19
19
  Bytes,
20
20
  BytesArray,
21
21
  BytesString,
22
- Chunk,
23
- Converter,
24
- DecimalInt,
25
- DecimalIntArray,
26
- Escaped,
27
22
  EscapedString,
28
- Group,
29
- Hex,
30
23
  HexArray,
31
- HexConvError,
32
24
  HexDump,
33
- Hexdump,
34
25
  HexInt,
35
26
  HexNumbersArray,
36
27
  HexString,
37
28
  Int,
38
29
  IntArray,
30
+ StructFormat,
31
+ Text,
32
+ # Short, ergonomic aliases for the format specifications above
33
+ ASCIIText,
34
+ Ascii85,
35
+ Base32,
36
+ Base64,
37
+ Base85,
38
+ Binary,
39
+ Bits,
40
+ DecimalInt,
41
+ DecimalIntArray,
42
+ Escaped,
43
+ Hex,
44
+ Hexdump,
39
45
  LargeHexNumber,
40
- Pad,
41
- Pipeline,
42
46
  RawString,
43
- Reverse,
44
47
  Struct,
45
- StructFormat,
46
- Text,
47
- Transform,
48
- Value,
48
+ # Pipeline transforms
49
+ Chunk,
50
+ Group,
51
+ Pad,
52
+ Reverse,
53
+ # One-shot conversion and pipeline builders
49
54
  convert,
55
+ infer,
56
+ pipeline,
57
+ # Source helpers
50
58
  from_ascii85,
51
59
  from_auto,
52
60
  from_base32,
@@ -66,57 +74,63 @@ from ._core import (
66
74
  from_int_array,
67
75
  from_struct,
68
76
  from_text,
69
- infer,
70
- pipeline,
71
77
  )
72
78
 
73
79
  __all__ = [
74
- "ASCIIText",
75
- "Auto",
80
+ # Core types and errors
76
81
  "AutoResult",
77
- "Ascii85",
82
+ "Converter",
83
+ "HexConvError",
84
+ "Pipeline",
85
+ "Transform",
86
+ "Value",
87
+ # Format specifications (explicit long-form names)
78
88
  "Ascii85String",
79
- "Base32",
89
+ "Auto",
80
90
  "Base32String",
81
91
  "Base64String",
82
- "Base64",
83
- "Base85",
84
92
  "Base85String",
85
- "Binary",
86
93
  "BinaryString",
87
94
  "BitArray",
88
- "Bits",
89
95
  "Bytes",
90
96
  "BytesArray",
91
97
  "BytesString",
92
- "Chunk",
93
- "Converter",
94
- "DecimalInt",
95
- "DecimalIntArray",
96
- "Escaped",
97
98
  "EscapedString",
98
- "Group",
99
- "Hex",
100
99
  "HexArray",
101
- "HexConvError",
102
100
  "HexDump",
103
- "Hexdump",
104
101
  "HexInt",
105
102
  "HexNumbersArray",
106
103
  "HexString",
107
104
  "Int",
108
105
  "IntArray",
106
+ "StructFormat",
107
+ "Text",
108
+ # Short, ergonomic aliases for the format specifications above
109
+ "ASCIIText",
110
+ "Ascii85",
111
+ "Base32",
112
+ "Base64",
113
+ "Base85",
114
+ "Binary",
115
+ "Bits",
116
+ "DecimalInt",
117
+ "DecimalIntArray",
118
+ "Escaped",
119
+ "Hex",
120
+ "Hexdump",
109
121
  "LargeHexNumber",
110
- "Pad",
111
- "Pipeline",
112
122
  "RawString",
113
- "Reverse",
114
123
  "Struct",
115
- "StructFormat",
116
- "Text",
117
- "Transform",
118
- "Value",
124
+ # Pipeline transforms
125
+ "Chunk",
126
+ "Group",
127
+ "Pad",
128
+ "Reverse",
129
+ # One-shot conversion and pipeline builders
119
130
  "convert",
131
+ "infer",
132
+ "pipeline",
133
+ # Source helpers
120
134
  "from_ascii85",
121
135
  "from_auto",
122
136
  "from_base32",
@@ -136,6 +150,4 @@ __all__ = [
136
150
  "from_int_array",
137
151
  "from_struct",
138
152
  "from_text",
139
- "infer",
140
- "pipeline",
141
153
  ]
@@ -3,12 +3,13 @@ from __future__ import annotations
3
3
  import ast
4
4
  import base64
5
5
  import binascii
6
+ import builtins
6
7
  import codecs
7
8
  import re
8
9
  import struct
9
- from collections.abc import Iterable
10
+ from collections.abc import Callable, Iterable
10
11
  from dataclasses import dataclass, field
11
- from typing import Any, Callable, Literal, TypeAlias, overload
12
+ from typing import Any, Literal, TypeAlias, overload
12
13
 
13
14
 
14
15
  class HexConvError(ValueError):
@@ -18,12 +19,12 @@ class HexConvError(ValueError):
18
19
  class _Format:
19
20
  """Base class for public format marker classes."""
20
21
 
21
- def __call__(self, value: Any) -> "Value":
22
+ def __call__(self, value: Any) -> Value:
22
23
  """Parse a value using this configured format."""
23
24
 
24
25
  return _parse(value, self)
25
26
 
26
- def __rshift__(self, other: Any) -> "Pipeline":
27
+ def __rshift__(self, other: Any) -> Pipeline:
27
28
  """Compose this format with a transform, target format, or pipeline."""
28
29
 
29
30
  return Pipeline(source=self) >> other
@@ -87,7 +88,7 @@ class HexArray(_Format):
87
88
  width: int | None = None,
88
89
  prefix: bool = False,
89
90
  uppercase: bool = False,
90
- endian: "Endian" = "big",
91
+ endian: Endian = "big",
91
92
  pad_odd: bool = True,
92
93
  ) -> None:
93
94
  self.width = width
@@ -118,7 +119,7 @@ class HexNumbersArray(_Format):
118
119
  self,
119
120
  *,
120
121
  width: int | None = 1,
121
- endian: "Endian" = "big",
122
+ endian: Endian = "big",
122
123
  signed: bool = False,
123
124
  ) -> None:
124
125
  self.width = width
@@ -140,7 +141,7 @@ class HexInt(_Format):
140
141
  *,
141
142
  min_width: int = 1,
142
143
  width: int | None = None,
143
- endian: "Endian" = "big",
144
+ endian: Endian = "big",
144
145
  signed: bool = False,
145
146
  pad_odd: bool = True,
146
147
  ) -> None:
@@ -174,7 +175,7 @@ class Int(_Format):
174
175
  *,
175
176
  min_width: int = 1,
176
177
  width: int | None = None,
177
- endian: "Endian" = "big",
178
+ endian: Endian = "big",
178
179
  signed: bool = False,
179
180
  ) -> None:
180
181
  self.min_width = min_width
@@ -205,7 +206,7 @@ class IntArray(_Format):
205
206
  self,
206
207
  *,
207
208
  width: int | None = None,
208
- endian: "Endian" = "big",
209
+ endian: Endian = "big",
209
210
  signed: bool = False,
210
211
  ) -> None:
211
212
  self.width = width
@@ -258,7 +259,7 @@ class BinaryString(_Format):
258
259
  sep: str = "",
259
260
  prefix: bool = False,
260
261
  pad_odd: bool = True,
261
- bit_order: "BitOrder" = "msb",
262
+ bit_order: BitOrder = "msb",
262
263
  ) -> None:
263
264
  self.sep = sep
264
265
  self.prefix = prefix
@@ -414,9 +415,9 @@ class BitArray(_Format):
414
415
  def __init__(
415
416
  self,
416
417
  *,
417
- bit_order: "BitOrder" = "msb",
418
+ bit_order: BitOrder = "msb",
418
419
  pad: bool = True,
419
- pad_side: "PadSide" = "left",
420
+ pad_side: PadSide = "left",
420
421
  ) -> None:
421
422
  self.bit_order = bit_order
422
423
  self.pad = pad
@@ -468,12 +469,12 @@ class Value:
468
469
  source: str | None = None
469
470
  meta: dict[str, Any] = field(default_factory=dict)
470
471
 
471
- def with_data(self, data: bytes, *, source: str | None = None) -> "Value":
472
+ def with_data(self, data: bytes, *, source: str | None = None) -> Value:
472
473
  """Return a copy with different bytes and preserved metadata."""
473
474
 
474
475
  return Value(bytes(data), source=source or self.source, meta=dict(self.meta))
475
476
 
476
- def with_meta(self, **meta: Any) -> "Value":
477
+ def with_meta(self, **meta: Any) -> Value:
477
478
  """Return a copy with additional metadata used by composable transforms."""
478
479
 
479
480
  merged = dict(self.meta)
@@ -773,17 +774,18 @@ class Value:
773
774
  raise HexConvError("chunk width cannot be None")
774
775
  return chunk_width
775
776
 
776
- # `int`/`integer` shadow the builtin, so they are defined after every method
777
- # whose annotations reference the builtin `int` (integer before int keeps
778
- # both of these return annotations resolving to the builtin too).
777
+ # ``int``/``integer`` are convenience shortcuts whose names shadow the builtin
778
+ # ``int``. They are declared after every method above so those methods keep
779
+ # annotating with the builtin, and they spell their own return type as
780
+ # ``builtins.int`` so their order here does not matter.
779
781
  @property
780
- def integer(self) -> int:
782
+ def int(self) -> builtins.int:
781
783
  """Shortcut for to_int()."""
782
784
 
783
785
  return self.to_int()
784
786
 
785
787
  @property
786
- def int(self) -> int:
788
+ def integer(self) -> builtins.int:
787
789
  """Shortcut for to_int()."""
788
790
 
789
791
  return self.to_int()
@@ -805,7 +807,7 @@ class Transform:
805
807
  def __call__(self, value: Any) -> Value:
806
808
  return self.apply(_ensure_value(value))
807
809
 
808
- def __rshift__(self, other: Any) -> "Pipeline":
810
+ def __rshift__(self, other: Any) -> Pipeline:
809
811
  return Pipeline(transforms=[self]) >> other
810
812
 
811
813
  def apply(self, value: Value) -> Value:
@@ -861,7 +863,7 @@ class Pad(Transform):
861
863
  width: int | None = None,
862
864
  block_size: int | None = None,
863
865
  byte: int = 0,
864
- ) -> "Pad":
866
+ ) -> Pad:
865
867
  return cls(width=width, block_size=block_size, byte=byte, side="left")
866
868
 
867
869
  @classmethod
@@ -871,7 +873,7 @@ class Pad(Transform):
871
873
  width: int | None = None,
872
874
  block_size: int | None = None,
873
875
  byte: int = 0,
874
- ) -> "Pad":
876
+ ) -> Pad:
875
877
  return cls(width=width, block_size=block_size, byte=byte, side="right")
876
878
 
877
879
  def apply(self, value: Value) -> Value:
@@ -908,23 +910,23 @@ class Pipeline:
908
910
  self.transforms = list(transforms or [])
909
911
  self.target = target
910
912
 
911
- def from_(self, source: FormatLike) -> "Pipeline":
913
+ def from_(self, source: FormatLike) -> Pipeline:
912
914
  return Pipeline(source=source, transforms=self.transforms, target=self.target)
913
915
 
914
- def to(self, target: FormatLike) -> "Pipeline":
916
+ def to(self, target: FormatLike) -> Pipeline:
915
917
  return Pipeline(source=self.source, transforms=self.transforms, target=target)
916
918
 
917
- def then(self, transform: Transform) -> "Pipeline":
919
+ def then(self, transform: Transform) -> Pipeline:
918
920
  return Pipeline(
919
921
  source=self.source,
920
922
  transforms=[*self.transforms, transform],
921
923
  target=self.target,
922
924
  )
923
925
 
924
- def chunk(self, width: int, *, strict: bool = True) -> "Pipeline":
926
+ def chunk(self, width: int, *, strict: bool = True) -> Pipeline:
925
927
  return self.then(Chunk(width, strict=strict))
926
928
 
927
- def group(self, width: int, *, strict: bool = True) -> "Pipeline":
929
+ def group(self, width: int, *, strict: bool = True) -> Pipeline:
928
930
  return self.then(Group(width, strict=strict))
929
931
 
930
932
  def pad(
@@ -934,13 +936,13 @@ class Pipeline:
934
936
  block_size: int | None = None,
935
937
  byte: int = 0,
936
938
  side: PadSide = "left",
937
- ) -> "Pipeline":
939
+ ) -> Pipeline:
938
940
  return self.then(Pad(width=width, block_size=block_size, byte=byte, side=side))
939
941
 
940
- def reverse(self) -> "Pipeline":
942
+ def reverse(self) -> Pipeline:
941
943
  return self.then(Reverse())
942
944
 
943
- def __rshift__(self, other: Any) -> "Pipeline":
945
+ def __rshift__(self, other: Any) -> Pipeline:
944
946
  if isinstance(other, Pipeline):
945
947
  source = self.source if self.source is not None else other.source
946
948
  return Pipeline(
@@ -1657,10 +1659,7 @@ def _format_options(format_: FormatLike, direction: str) -> dict[str, Any]:
1657
1659
  def _ensure_value(value: Any) -> Value:
1658
1660
  if isinstance(value, Value):
1659
1661
  return value
1660
- parsed = from_auto(value)
1661
- if isinstance(parsed, AutoResult):
1662
- return parsed.value
1663
- return parsed
1662
+ return from_auto(value)
1664
1663
 
1665
1664
 
1666
1665
  def _validate_endian(endian: Endian) -> None:
@@ -1850,6 +1849,8 @@ def _int_to_bytes(
1850
1849
 
1851
1850
 
1852
1851
  def _signed_int_to_minimal_bytes(value: int, *, min_width: int, endian: Endian) -> bytes:
1852
+ # Two's complement needs a bit beyond the magnitude for the sign, so start
1853
+ # one byte wider than the magnitude and widen further until the value fits.
1853
1854
  candidate = max(min_width, 1, (abs(value).bit_length() + 8) // 8)
1854
1855
  for width in range(candidate, candidate + 2):
1855
1856
  try:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hexconv
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: Pythonic conversion toolkit for bytes, hex, integers, arrays, binary strings, base64, and text.
5
5
  Author: hexconv contributors
6
6
  License-Expression: MIT
@@ -24,7 +24,9 @@ License-File: LICENSE
24
24
  Provides-Extra: dev
25
25
  Requires-Dist: build; extra == "dev"
26
26
  Requires-Dist: hypothesis; extra == "dev"
27
+ Requires-Dist: mypy; extra == "dev"
27
28
  Requires-Dist: pytest; extra == "dev"
29
+ Requires-Dist: ruff; extra == "dev"
28
30
  Requires-Dist: twine; extra == "dev"
29
31
  Dynamic: license-file
30
32
 
@@ -320,6 +322,23 @@ Available format specs all work in their simplest bare form:
320
322
  - `Bits()`
321
323
  - `Auto()`
322
324
 
325
+ The short names are ergonomic aliases of the explicit long-form specs — pick
326
+ whichever reads better, they resolve to the same format:
327
+
328
+ | Short | Long form |
329
+ | --- | --- |
330
+ | `Hex` | `HexString` |
331
+ | `Binary` | `BinaryString` |
332
+ | `Base64` / `Base32` / `Base85` / `Ascii85` | `Base64String` / `Base32String` / `Base85String` / `Ascii85String` |
333
+ | `Escaped` | `EscapedString` |
334
+ | `Hexdump` | `HexDump` |
335
+ | `Struct` | `StructFormat` |
336
+ | `Bits` | `BitArray` |
337
+ | `DecimalInt` / `DecimalIntArray` | `Int` / `IntArray` |
338
+ | `LargeHexNumber` | `HexInt` |
339
+ | `ASCIIText` / `RawString` | `Text` |
340
+ | `Group` (transform) | `Chunk` |
341
+
323
342
  Add options only when you need control over formatting or parsing:
324
343
 
325
344
  - Display: `Hex(sep=" ", prefix=True, uppercase=True)`, `HexArray(width=2)`.
@@ -2,5 +2,7 @@
2
2
  [dev]
3
3
  build
4
4
  hypothesis
5
+ mypy
5
6
  pytest
7
+ ruff
6
8
  twine
File without changes
File without changes
File without changes
File without changes
File without changes