lvbitpack 1.0.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 (53) hide show
  1. lvbitpack-1.0.0/.gitignore +12 -0
  2. lvbitpack-1.0.0/CHANGELOG.md +81 -0
  3. lvbitpack-1.0.0/CONTRIBUTING.md +19 -0
  4. lvbitpack-1.0.0/GUIDE.md +200 -0
  5. lvbitpack-1.0.0/LICENSE +21 -0
  6. lvbitpack-1.0.0/PKG-INFO +142 -0
  7. lvbitpack-1.0.0/README.md +103 -0
  8. lvbitpack-1.0.0/pyproject.toml +77 -0
  9. lvbitpack-1.0.0/requirements-dev.txt +3 -0
  10. lvbitpack-1.0.0/src/lvbitpack/__init__.py +60 -0
  11. lvbitpack-1.0.0/src/lvbitpack/__main__.py +7 -0
  12. lvbitpack-1.0.0/src/lvbitpack/_bytes.py +96 -0
  13. lvbitpack-1.0.0/src/lvbitpack/_cli.py +154 -0
  14. lvbitpack-1.0.0/src/lvbitpack/_cli_io.py +83 -0
  15. lvbitpack-1.0.0/src/lvbitpack/_errors.py +138 -0
  16. lvbitpack-1.0.0/src/lvbitpack/_flatten.py +56 -0
  17. lvbitpack-1.0.0/src/lvbitpack/_modes.py +49 -0
  18. lvbitpack-1.0.0/src/lvbitpack/_number.py +88 -0
  19. lvbitpack-1.0.0/src/lvbitpack/_pack.py +72 -0
  20. lvbitpack-1.0.0/src/lvbitpack/_text.py +63 -0
  21. lvbitpack-1.0.0/src/lvbitpack/_types.py +150 -0
  22. lvbitpack-1.0.0/src/lvbitpack/py.typed +0 -0
  23. lvbitpack-1.0.0/tests/fixtures/labview2026/boolean_array_to_number.json +1 -0
  24. lvbitpack-1.0.0/tests/fixtures/labview2026/datalog_bool_len0.dat +0 -0
  25. lvbitpack-1.0.0/tests/fixtures/labview2026/datalog_bool_len1.dat +0 -0
  26. lvbitpack-1.0.0/tests/fixtures/labview2026/datalog_bool_len17.dat +0 -0
  27. lvbitpack-1.0.0/tests/fixtures/labview2026/datalog_bool_len300.dat +0 -0
  28. lvbitpack-1.0.0/tests/fixtures/labview2026/datalog_bool_len8.dat +0 -0
  29. lvbitpack-1.0.0/tests/fixtures/labview2026/datalog_bool_len9.dat +0 -0
  30. lvbitpack-1.0.0/tests/fixtures/labview2026/manifest.json +200 -0
  31. lvbitpack-1.0.0/tests/fixtures/labview2026/number_to_boolean_array.json +1 -0
  32. lvbitpack-1.0.0/tests/test_bytes.py +104 -0
  33. lvbitpack-1.0.0/tests/test_cli.py +122 -0
  34. lvbitpack-1.0.0/tests/test_cli_process.py +50 -0
  35. lvbitpack-1.0.0/tests/test_contract.py +172 -0
  36. lvbitpack-1.0.0/tests/test_contract_edges.py +47 -0
  37. lvbitpack-1.0.0/tests/test_contract_strength.py +145 -0
  38. lvbitpack-1.0.0/tests/test_contract_strength_cli.py +128 -0
  39. lvbitpack-1.0.0/tests/test_contract_strength_three.py +137 -0
  40. lvbitpack-1.0.0/tests/test_contract_strength_two.py +105 -0
  41. lvbitpack-1.0.0/tests/test_docs.py +105 -0
  42. lvbitpack-1.0.0/tests/test_flatten.py +64 -0
  43. lvbitpack-1.0.0/tests/test_fuzz.py +113 -0
  44. lvbitpack-1.0.0/tests/test_hostile.py +186 -0
  45. lvbitpack-1.0.0/tests/test_hostile_five.py +64 -0
  46. lvbitpack-1.0.0/tests/test_hostile_four.py +121 -0
  47. lvbitpack-1.0.0/tests/test_hostile_more.py +128 -0
  48. lvbitpack-1.0.0/tests/test_hostile_three.py +138 -0
  49. lvbitpack-1.0.0/tests/test_number.py +147 -0
  50. lvbitpack-1.0.0/tests/test_pack.py +87 -0
  51. lvbitpack-1.0.0/tests/test_packaging.py +90 -0
  52. lvbitpack-1.0.0/tests/test_real_cli_e2e.py +113 -0
  53. lvbitpack-1.0.0/tests/test_real_labview.py +102 -0
@@ -0,0 +1,12 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ venv/
5
+ build/
6
+ dist/
7
+ *.egg-info/
8
+ .pytest_cache/
9
+ .mypy_cache/
10
+ .ruff_cache/
11
+ .coverage
12
+ htmlcov/
@@ -0,0 +1,81 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
5
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [1.0.0] - 2026-10-14
10
+
11
+ First stable release, and the first one published on PyPI. The public API is the 21 names
12
+ in `lvbitpack.__all__`; their names, signatures, defaults and error type are now stable.
13
+
14
+ ### Changed (from 0.2.0)
15
+
16
+ - `pack_flattened` and `unpack_flattened` now read and write LabVIEW's real flattened
17
+ Boolean array: a big-endian I32 count, then one byte per element (0x01 / 0x00). In 0.2.0
18
+ they wrote the count followed by bit-packed bytes, which LabVIEW cannot unflatten.
19
+ `unpack_flattened` reads any nonzero byte as TRUE and refuses a negative count, missing
20
+ elements and bytes left over after the last element.
21
+ - The byte functions (`number_to_bytes`, `bytes_to_number`, `bools_to_bytes`,
22
+ `bytes_to_bools`) default to big-endian, LabVIEW's flattened order; 0.2.0 defaulted to
23
+ little-endian.
24
+ - Every refusal raises the new `BitpackError` (a `ValueError`). In 0.2.0 some bad input
25
+ leaked `TypeError` or `AttributeError` from inside, and some was silently accepted.
26
+ - Boolean arrays are checked: a `str` or bytes object is refused (0.2.0 read each
27
+ character's truthiness, so `"0101"` gave 15), and elements must be booleans or 0/1.
28
+ - Arrays may be any finite iterable; 0.2.0 needed indexing, so generators failed, and
29
+ `bools_to_number` refused NumPy arrays ("truth value of an array is ambiguous").
30
+ - `IntType` refuses a non-`int` or `bool` width and a non-`bool` `signed`; 0.2.0 accepted
31
+ `IntType(8.0, "yes")`. `IntType.parse` accepts only the eight names (with `u`, `uint`, `i`,
32
+ `int` prefixes); 0.2.0 accepted forms such as `"u08"`. A NumPy or other `__index__`
33
+ width is accepted and stored as `int`, and a NumPy boolean `signed` is stored as `bool`.
34
+ `IntType.contains` answers False for `bool` and floats (0.2.0 said True for
35
+ `contains(1.0)` and `contains(True)`) and True for in-range `__index__` integers.
36
+ - Byte arguments accept any buffer-protocol object and read its raw bytes, so a buffer
37
+ of wide items (an `array.array("H")`, a NumPy `uint16` array) is measured in bytes; 0.2.0
38
+ measured it in items. Lists of integers, which 0.2.0 read as bytes, are refused, and so
39
+ are buffers of Python objects (a NumPy `object` array), which 0.2.0 read as pointer bytes.
40
+ - `number` and `count` are typed `SupportsIndex`, so NumPy integers pass a type checker.
41
+ - `SignExtension(...)` with anything but a member or one of its three names raises
42
+ `BitpackError` (still a `ValueError`). `sign_extension=` takes only a `SignExtension`
43
+ member: 0.2.0 silently treated a string such as `"never"` as `IF_SIGNED`.
44
+ - `IntType` and `SignExtension` are defined in private modules other than 0.2.0's
45
+ (`lvbitpack._types`, `lvbitpack._modes`); pickles made by 0.2.0 still load.
46
+ - Numbers are checked: `bool` and non-integers are refused, `__index__` integers (NumPy)
47
+ accepted, and messages never print a huge integer (0.2.0 failed inside its own message
48
+ for integers beyond Python's 4300-digit printing limit).
49
+ - The command line prefixes errors with `lvbitpack: error:`, bounds every message, exits 1
50
+ quietly when its output pipe closes, and runs as `python -m lvbitpack`. An unknown
51
+ `--type` is a usage error (exit 2, was 1). Bit strings also ignore `_`; `unpack` refuses
52
+ numbers longer than 100 characters or written with non-ASCII digits, and reads negative
53
+ prefixed numbers such as `-0x2` on every supported Python.
54
+ - Public functions report `lvbitpack` as their module in `help()`; the classes keep the
55
+ module they are defined in, so source lookup and pickles find them.
56
+
57
+ ### Added
58
+
59
+ - `BitpackError`.
60
+ - `bit_order="msb" | "lsb"` on `pack_bools` and `unpack_bools`; `unpack_bools`'s `count` is
61
+ now optional (every bit by default).
62
+ - `flatten` and `unflatten` commands.
63
+ - `GUIDE.md` with executed examples.
64
+
65
+ ## [0.2.0] - not released
66
+
67
+ - `bools_to_number` and `number_to_bools`: LabVIEW Boolean Array To Number and
68
+ Number To Boolean Array, element 0 the least significant bit, U8/U16/U32/U64 and
69
+ signed I8/I16/I32/I64 two's complement, extra elements dropped, and the three
70
+ documented sign-extension modes for short arrays.
71
+ - `SignExtension` enum and the typed `IntType` value object with the `U8` to `I64`
72
+ presets, bounds, mask, and a `parse` helper.
73
+ - `number_to_bytes`, `bytes_to_number`, `bools_to_bytes`, and `bytes_to_bools`.
74
+ - `lvbitpack` command line interface: `pack` and `unpack`.
75
+
76
+ ## [0.1.0] - not released
77
+
78
+ - `pack_bools` and `unpack_bools`: MSB-first bit packing with zero padding of the
79
+ final byte and count-aware unpacking.
80
+ - `pack_flattened` and `unpack_flattened`: a four-byte big-endian element count
81
+ followed by the packed bits.
@@ -0,0 +1,19 @@
1
+ # Contributing
2
+
3
+ Thanks for taking the time. This project keeps a small, predictable shape.
4
+
5
+ ## Setup
6
+
7
+ ```
8
+ pip install -r requirements-dev.txt
9
+ ```
10
+
11
+ ## Before you open a pull request
12
+
13
+ - Keep public API in `src/lvbitpack/__init__.py`; internals live in `_`-prefixed modules.
14
+ - Add a test for the behaviour you change. Tests inject time, disk and network; they
15
+ never touch the real thing.
16
+ - Run `scripts/check.ps1` (format, lint, types, tests) and make sure it passes.
17
+ - One logical change per commit, imperative subject line.
18
+
19
+ Open an issue first if the change is large or shifts the public API.
@@ -0,0 +1,200 @@
1
+ # lvbitpack guide
2
+
3
+ Every example below runs as written; the test suite executes them.
4
+
5
+ ## Boolean Array To Number
6
+
7
+ `bools_to_number(values, int_type, *, sign_extension=SignExtension.IF_SIGNED)` reads the
8
+ array as the binary form of one integer. Element 0 is the least significant bit.
9
+
10
+ ```pycon
11
+ >>> from lvbitpack import I8, I16, U8, SignExtension, bools_to_number
12
+ >>> bools_to_number([False, True], U8)
13
+ 2
14
+ >>> bools_to_number([True] * 8 + [False, True], U8)
15
+ 255
16
+ >>> bools_to_number([], I16)
17
+ 0
18
+
19
+ ```
20
+
21
+ Elements past the width are dropped. A shorter array is padded above its last element as
22
+ the sign extension mode (set by right-clicking the node in LabVIEW) says:
23
+
24
+ | Mode | Unsigned output pads with | Signed output pads with |
25
+ | --- | --- | --- |
26
+ | `SignExtension.NEVER` | FALSE | FALSE |
27
+ | `SignExtension.IF_SIGNED` (LabVIEW's default) | FALSE | the last element |
28
+ | `SignExtension.ALWAYS` | the last element | the last element |
29
+
30
+ ```pycon
31
+ >>> from lvbitpack import I8, U8, SignExtension, bools_to_number
32
+ >>> array = [False, False, True, False, True]
33
+ >>> [bools_to_number(array, U8, sign_extension=mode) for mode in SignExtension]
34
+ [20, 20, 244]
35
+ >>> [bools_to_number(array, I8, sign_extension=mode) for mode in SignExtension]
36
+ [20, -12, -12]
37
+
38
+ ```
39
+
40
+ A signed output is the two's complement value; an empty array is 0 in every mode. The test
41
+ suite replays about 2,900 results of this node and 200 of Number To Boolean Array produced
42
+ by LabVIEW 2026 (all eight types, the three modes, arrays of 0 to 65 elements; see
43
+ `tests/fixtures/labview2026`). There a node with no output representation set gave a U32
44
+ (it was inserted by editing a VI file; NI does not document the default).
45
+
46
+ ## Number To Boolean Array
47
+
48
+ `number_to_bools(number, int_type)` returns one element per bit of the type, element 0 the
49
+ least significant bit of the two's complement form. A number outside the type raises
50
+ `BitpackError` (LabVIEW would have coerced it to the wire's type first).
51
+
52
+ ```pycon
53
+ >>> from lvbitpack import I8, U64, number_to_bools
54
+ >>> number_to_bools(-2, I8)
55
+ [False, True, True, True, True, True, True, True]
56
+ >>> len(number_to_bools(5, U64))
57
+ 64
58
+
59
+ ```
60
+
61
+ ## Integer types
62
+
63
+ `IntType(bits, signed)` takes 8, 16, 32 or 64 bits. `U8`, `U16`, `U32`, `U64`, `I8`, `I16`,
64
+ `I32` and `I64` are ready-made, and `IntType.parse` reads names in any case.
65
+
66
+ ```pycon
67
+ >>> from lvbitpack import IntType, I32
68
+ >>> IntType.parse("int32") == I32
69
+ True
70
+ >>> I32.name, I32.min, I32.max, hex(I32.mask)
71
+ ('i32', -2147483648, 2147483647, '0xffffffff')
72
+ >>> I32.contains(2**31)
73
+ False
74
+
75
+ ```
76
+
77
+ ## Bytes of the number
78
+
79
+ `number_to_bytes`, `bytes_to_number`, `bools_to_bytes` and `bytes_to_bools` lay the number
80
+ out in exactly `bits // 8` bytes. The default is big-endian, LabVIEW's flattened order;
81
+ pass `byteorder="little"` for the memory order of x86 and ARM machines. The element to bit
82
+ mapping never changes with byte order.
83
+
84
+ ```pycon
85
+ >>> from lvbitpack import I32, U16, bools_to_bytes, bytes_to_number, number_to_bytes
86
+ >>> number_to_bytes(-19, I32).hex()
87
+ 'ffffffed'
88
+ >>> bytes_to_number(bytes.fromhex("edffffff"), I32, byteorder="little")
89
+ -19
90
+ >>> bools_to_bytes([False, True], U16, byteorder="little")
91
+ b'\x02\x00'
92
+
93
+ ```
94
+
95
+ ## Flattened Boolean arrays
96
+
97
+ `pack_flattened` writes what LabVIEW's Flatten To String writes for a 1-D Boolean array with
98
+ its "prepend array or string size?" input left at its default (TRUE): a big-endian I32
99
+ element count, then one byte per element, 0x01 for TRUE and 0x00 for FALSE.
100
+ `unpack_flattened` reads it back; any nonzero element byte is TRUE, and the input must hold
101
+ exactly the counted elements.
102
+
103
+ ```pycon
104
+ >>> from lvbitpack import pack_flattened, unpack_flattened
105
+ >>> pack_flattened([True, False, True, True]).hex()
106
+ '0000000401000101'
107
+ >>> unpack_flattened(bytes.fromhex("00000002ff00"))
108
+ [True, False]
109
+
110
+ ```
111
+
112
+ ## Dense bit packing
113
+
114
+ `pack_bools` stores eight elements per byte with no header, filling unused bits of the last
115
+ byte with zeros. `bit_order="msb"` (default) puts element 0 in bit 7 of byte 0; `"lsb"` puts
116
+ it in bit 0, so each byte equals Boolean Array To Number of its eight elements as a U8.
117
+ `unpack_bools(data, count=None)` reads `count` elements, or every bit when `count` is None.
118
+ Current LabVIEW stores and flattens a Boolean as a whole byte; this form is for protocols
119
+ and files that pack bits.
120
+
121
+ ```pycon
122
+ >>> from lvbitpack import pack_bools, unpack_bools
123
+ >>> pack_bools([True] * 9), pack_bools([True] * 9, bit_order="lsb")
124
+ (b'\xff\x80', b'\xff\x01')
125
+ >>> unpack_bools(b"\xd0", 4)
126
+ [True, True, False, True]
127
+
128
+ ```
129
+
130
+ ## What counts as input
131
+
132
+ - A Boolean array is any finite iterable (list, tuple, generator, NumPy array) of `True`,
133
+ `False`, 0, 1 (or `__index__` objects giving 0 or 1) or NumPy booleans; the hints say
134
+ `Iterable[bool | int]`. `str`, `bytes`, `bytearray` and `memoryview` are refused: `"0101"`
135
+ is not four Booleans. A set has no element order, so pass a sequence.
136
+ - Numbers, counts and `IntType` widths accept any `__index__` integer except `bool`
137
+ (`number` and `count` are typed `SupportsIndex`; elsewhere a type checker wants `int(...)`
138
+ or `bool(...)` around NumPy scalars). `IntType.contains` answers False for anything else.
139
+ - Bytes are any buffer-protocol object (`bytes`, `bytearray`, `memoryview`, `array.array`,
140
+ NumPy arrays, ctypes objects), read as raw bytes in memory order. Text, lists and buffers
141
+ of Python objects (a NumPy `object` array) are refused.
142
+
143
+ ```pycon
144
+ >>> import array
145
+ >>> from lvbitpack import U16, bytes_to_number
146
+ >>> bytes_to_number(array.array("B", [1, 0]), U16)
147
+ 256
148
+
149
+ ```
150
+
151
+ ## Errors
152
+
153
+ Every refusal raises `BitpackError`, a subclass of `ValueError`, including
154
+ `SignExtension(value)` for any value that is not a member or one of its three names.
155
+ Messages are printable ASCII, quote at most 40 characters of a bad value and describe
156
+ integers wider than 128 bits by their size. Checks use an object's real type, never a
157
+ `__class__` it claims. An object that cannot be iterated, indexed or read as a buffer at
158
+ all is refused; any other exception your own object raises (from `__index__`, `__iter__`,
159
+ while iterating, or from `__buffer__` on Python 3.12 and later, except a `ValueError`)
160
+ passes through unchanged, as do ordinary call mistakes such as a missing argument.
161
+
162
+ | Situation | Message starts with |
163
+ | --- | --- |
164
+ | number outside the type | `<n> is out of range for <type>` |
165
+ | element that is not a Boolean | `element <i> must be a boolean` |
166
+ | text or bytes as the array | `values must be an iterable of booleans` |
167
+ | wrong byte count for a number | `<type> needs exactly <n> bytes` |
168
+ | short flattened array | `a flattened array starts`, `the count says` |
169
+ | negative count, extra bytes | `the element count is negative`, `<n> byte(s) follow(s)` |
170
+ | bad option or `int_type` | the parameter's name |
171
+
172
+ ## Command line
173
+
174
+ ```
175
+ $ lvbitpack pack 01 --type u8
176
+ 2
177
+ $ lvbitpack pack 01 --type i8
178
+ -2
179
+ $ lvbitpack pack 0101 --type i8 --sign-extension always
180
+ -6
181
+ $ lvbitpack pack 1 --type u16 --base hex
182
+ 0x1
183
+ $ lvbitpack unpack 0x80 --type u8
184
+ 00000001
185
+ $ lvbitpack flatten 1011
186
+ 0000000401000101
187
+ $ lvbitpack unflatten 0000000401000101
188
+ 1011
189
+ ```
190
+
191
+ Bit strings list element 0 first; `1`, `t`, `T` are TRUE, `0`, `f`, `F` FALSE, and spaces,
192
+ commas and underscores are ignored. `unpack` reads decimal or `0x`, `0o`, `0b` prefixed
193
+ numbers in ASCII digits, negative ones included (`unpack -0x80 -t i8`). Data errors print
194
+ `lvbitpack: error: ...` and exit 1; usage errors, an unknown `--type` included, exit 2.
195
+
196
+ ## Scope
197
+
198
+ Boolean Array To Number can also produce a fixed-point number, and Number To Boolean Array
199
+ accepts one; lvbitpack covers the eight integer types. Multi-dimensional arrays are not
200
+ flattened here.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Javier Lianes
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,142 @@
1
+ Metadata-Version: 2.5
2
+ Name: lvbitpack
3
+ Version: 1.0.0
4
+ Summary: LabVIEW Boolean arrays in pure Python: Boolean Array To Number with its sign extension modes, Number To Boolean Array, flattening and bit packing
5
+ Project-URL: Homepage, https://lianes8studio.com
6
+ Project-URL: Source, https://github.com/JaviLianes8/lvbitpack
7
+ Project-URL: Issues, https://github.com/JaviLianes8/lvbitpack/issues
8
+ Project-URL: Changelog, https://github.com/JaviLianes8/lvbitpack/blob/main/CHANGELOG.md
9
+ Project-URL: LinkedIn, https://www.linkedin.com/in/jlianes/
10
+ Project-URL: Funding, https://buymeacoffee.com/jlianesglrs
11
+ Author-email: Javier Lianes <javier@lianes8studio.com>
12
+ License-Expression: MIT
13
+ License-File: LICENSE
14
+ Keywords: binary,bit,bitpack,boolean,flatten,labview,ni,sign-extension
15
+ Classifier: Development Status :: 5 - Production/Stable
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Intended Audience :: Science/Research
18
+ Classifier: Operating System :: OS Independent
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: Programming Language :: Python :: 3.10
22
+ Classifier: Programming Language :: Python :: 3.11
23
+ Classifier: Programming Language :: Python :: 3.12
24
+ Classifier: Programming Language :: Python :: 3.13
25
+ Classifier: Programming Language :: Python :: 3.14
26
+ Classifier: Topic :: Scientific/Engineering
27
+ Classifier: Topic :: Software Development :: Libraries
28
+ Classifier: Typing :: Typed
29
+ Requires-Python: >=3.10
30
+ Provides-Extra: dev
31
+ Requires-Dist: build; extra == 'dev'
32
+ Requires-Dist: editables; extra == 'dev'
33
+ Requires-Dist: hatchling; extra == 'dev'
34
+ Requires-Dist: mypy; extra == 'dev'
35
+ Requires-Dist: pytest; extra == 'dev'
36
+ Requires-Dist: ruff; extra == 'dev'
37
+ Requires-Dist: twine; extra == 'dev'
38
+ Description-Content-Type: text/markdown
39
+
40
+ # lvbitpack
41
+
42
+ LabVIEW Boolean arrays in pure Python: **Boolean Array To Number** with its three sign
43
+ extension modes, **Number To Boolean Array**, the flattened form LabVIEW writes for a Boolean
44
+ array, and compact bit packing. No dependencies, fully typed, Python 3.10 or newer.
45
+
46
+ The full reference is the [guide](https://github.com/JaviLianes8/lvbitpack/blob/main/GUIDE.md);
47
+ every release is described in the
48
+ [changelog](https://github.com/JaviLianes8/lvbitpack/blob/main/CHANGELOG.md).
49
+
50
+ ## Install
51
+
52
+ ```
53
+ pip install lvbitpack
54
+ ```
55
+
56
+ ## The problem
57
+
58
+ A LabVIEW program that reads digital lines, status registers or flag words often turns a
59
+ Boolean array into one integer with Boolean Array To Number, and back with Number To Boolean
60
+ Array. Element 0 is the least significant bit, elements past the width are dropped, and a
61
+ short array is padded by a per-node setting (Sign Extension Mode) that most people never
62
+ look at, yet it decides whether `{FALSE, TRUE}` as an I8 is 2 or -2. Getting the same
63
+ numbers in Python means reproducing those rules exactly.
64
+
65
+ ## Quick start
66
+
67
+ ```python
68
+ from lvbitpack import I8, U8, U16, SignExtension, bools_to_number, number_to_bools
69
+
70
+ # Element 0 is the least significant bit.
71
+ assert bools_to_number([False, True], U8) == 2
72
+
73
+ # LabVIEW's default mode pads a signed output with the last element: {F, T} as I8 is -2.
74
+ assert bools_to_number([False, True], I8) == -2
75
+ assert bools_to_number([False, True, False, True], I8, sign_extension=SignExtension.NEVER) == 10
76
+ assert bools_to_number([False, True, False, True], I8, sign_extension=SignExtension.ALWAYS) == -6
77
+
78
+ # Elements beyond the width are dropped.
79
+ assert bools_to_number([True] * 9, U8) == 255
80
+
81
+ # The inverse returns one element per bit of the type.
82
+ assert number_to_bools(-2, I8) == [False, True, True, True, True, True, True, True]
83
+ assert len(number_to_bools(1, U16)) == 16
84
+ ```
85
+
86
+ Bytes, LabVIEW's flattened array and dense bit packing:
87
+
88
+ ```python
89
+ from lvbitpack import U16, bools_to_bytes, pack_bools, pack_flattened, unpack_flattened
90
+
91
+ # The number's bytes, big-endian by default like LabVIEW's Flatten To String.
92
+ assert bools_to_bytes([False, True], U16) == b"\x00\x02"
93
+ assert bools_to_bytes([False, True], U16, byteorder="little") == b"\x02\x00"
94
+
95
+ # Flatten To String of a Boolean array: an I32 count, then one byte per element.
96
+ assert pack_flattened([True, False, True, True]).hex() == "0000000401000101"
97
+ assert unpack_flattened(bytes.fromhex("0000000401000101")) == [True, False, True, True]
98
+
99
+ # Eight elements per byte, first element in the high bit (or the low bit with "lsb").
100
+ assert pack_bools([True, True, False, True]) == b"\xd0"
101
+ assert pack_bools([True, True, False, True], bit_order="lsb") == b"\x0b"
102
+ ```
103
+
104
+ Anything refused (a value out of range, a string where a Boolean array belongs, a truncated
105
+ flattened array) raises `lvbitpack.BitpackError`, a `ValueError`.
106
+
107
+ ## Command line
108
+
109
+ ```
110
+ $ lvbitpack pack 01 --type i8
111
+ -2
112
+ $ lvbitpack pack 0101 --type i8 --sign-extension never
113
+ 10
114
+ $ lvbitpack unpack -2 --type i8
115
+ 01111111
116
+ $ lvbitpack flatten 1011
117
+ 0000000401000101
118
+ ```
119
+
120
+ Bit strings list element 0 first. `python -m lvbitpack` works too.
121
+
122
+ ## Why this library
123
+
124
+ - It follows LabVIEW's own rules: the three Sign Extension Mode settings, truncation of long
125
+ arrays, the 8, 16, 32 and 64-bit signed and unsigned outputs, and the flattened Boolean
126
+ array layout, checked against NI's documentation and against output of LabVIEW 2026.
127
+ - NumPy's `packbits` packs 0/1 arrays into `uint8` bytes (high or low bit first) but has no
128
+ notion of LabVIEW's integer widths or sign extension, and needs NumPy. `bitarray` (a C
129
+ extension) and `bitstring` (which depends on `tibs`) are general bit containers; neither
130
+ reproduces Boolean Array To Number. lvbitpack is small, pure Python and dependency free.
131
+
132
+ What it does not cover: Boolean Array To Number with a fixed-point output (see the guide).
133
+
134
+ ## License
135
+
136
+ MIT. See [LICENSE](https://github.com/JaviLianes8/lvbitpack/blob/main/LICENSE).
137
+
138
+ ## Links
139
+
140
+ - Website: https://lianes8studio.com
141
+ - GitHub: https://github.com/JaviLianes8
142
+ - LinkedIn: https://www.linkedin.com/in/jlianes/
@@ -0,0 +1,103 @@
1
+ # lvbitpack
2
+
3
+ LabVIEW Boolean arrays in pure Python: **Boolean Array To Number** with its three sign
4
+ extension modes, **Number To Boolean Array**, the flattened form LabVIEW writes for a Boolean
5
+ array, and compact bit packing. No dependencies, fully typed, Python 3.10 or newer.
6
+
7
+ The full reference is the [guide](https://github.com/JaviLianes8/lvbitpack/blob/main/GUIDE.md);
8
+ every release is described in the
9
+ [changelog](https://github.com/JaviLianes8/lvbitpack/blob/main/CHANGELOG.md).
10
+
11
+ ## Install
12
+
13
+ ```
14
+ pip install lvbitpack
15
+ ```
16
+
17
+ ## The problem
18
+
19
+ A LabVIEW program that reads digital lines, status registers or flag words often turns a
20
+ Boolean array into one integer with Boolean Array To Number, and back with Number To Boolean
21
+ Array. Element 0 is the least significant bit, elements past the width are dropped, and a
22
+ short array is padded by a per-node setting (Sign Extension Mode) that most people never
23
+ look at, yet it decides whether `{FALSE, TRUE}` as an I8 is 2 or -2. Getting the same
24
+ numbers in Python means reproducing those rules exactly.
25
+
26
+ ## Quick start
27
+
28
+ ```python
29
+ from lvbitpack import I8, U8, U16, SignExtension, bools_to_number, number_to_bools
30
+
31
+ # Element 0 is the least significant bit.
32
+ assert bools_to_number([False, True], U8) == 2
33
+
34
+ # LabVIEW's default mode pads a signed output with the last element: {F, T} as I8 is -2.
35
+ assert bools_to_number([False, True], I8) == -2
36
+ assert bools_to_number([False, True, False, True], I8, sign_extension=SignExtension.NEVER) == 10
37
+ assert bools_to_number([False, True, False, True], I8, sign_extension=SignExtension.ALWAYS) == -6
38
+
39
+ # Elements beyond the width are dropped.
40
+ assert bools_to_number([True] * 9, U8) == 255
41
+
42
+ # The inverse returns one element per bit of the type.
43
+ assert number_to_bools(-2, I8) == [False, True, True, True, True, True, True, True]
44
+ assert len(number_to_bools(1, U16)) == 16
45
+ ```
46
+
47
+ Bytes, LabVIEW's flattened array and dense bit packing:
48
+
49
+ ```python
50
+ from lvbitpack import U16, bools_to_bytes, pack_bools, pack_flattened, unpack_flattened
51
+
52
+ # The number's bytes, big-endian by default like LabVIEW's Flatten To String.
53
+ assert bools_to_bytes([False, True], U16) == b"\x00\x02"
54
+ assert bools_to_bytes([False, True], U16, byteorder="little") == b"\x02\x00"
55
+
56
+ # Flatten To String of a Boolean array: an I32 count, then one byte per element.
57
+ assert pack_flattened([True, False, True, True]).hex() == "0000000401000101"
58
+ assert unpack_flattened(bytes.fromhex("0000000401000101")) == [True, False, True, True]
59
+
60
+ # Eight elements per byte, first element in the high bit (or the low bit with "lsb").
61
+ assert pack_bools([True, True, False, True]) == b"\xd0"
62
+ assert pack_bools([True, True, False, True], bit_order="lsb") == b"\x0b"
63
+ ```
64
+
65
+ Anything refused (a value out of range, a string where a Boolean array belongs, a truncated
66
+ flattened array) raises `lvbitpack.BitpackError`, a `ValueError`.
67
+
68
+ ## Command line
69
+
70
+ ```
71
+ $ lvbitpack pack 01 --type i8
72
+ -2
73
+ $ lvbitpack pack 0101 --type i8 --sign-extension never
74
+ 10
75
+ $ lvbitpack unpack -2 --type i8
76
+ 01111111
77
+ $ lvbitpack flatten 1011
78
+ 0000000401000101
79
+ ```
80
+
81
+ Bit strings list element 0 first. `python -m lvbitpack` works too.
82
+
83
+ ## Why this library
84
+
85
+ - It follows LabVIEW's own rules: the three Sign Extension Mode settings, truncation of long
86
+ arrays, the 8, 16, 32 and 64-bit signed and unsigned outputs, and the flattened Boolean
87
+ array layout, checked against NI's documentation and against output of LabVIEW 2026.
88
+ - NumPy's `packbits` packs 0/1 arrays into `uint8` bytes (high or low bit first) but has no
89
+ notion of LabVIEW's integer widths or sign extension, and needs NumPy. `bitarray` (a C
90
+ extension) and `bitstring` (which depends on `tibs`) are general bit containers; neither
91
+ reproduces Boolean Array To Number. lvbitpack is small, pure Python and dependency free.
92
+
93
+ What it does not cover: Boolean Array To Number with a fixed-point output (see the guide).
94
+
95
+ ## License
96
+
97
+ MIT. See [LICENSE](https://github.com/JaviLianes8/lvbitpack/blob/main/LICENSE).
98
+
99
+ ## Links
100
+
101
+ - Website: https://lianes8studio.com
102
+ - GitHub: https://github.com/JaviLianes8
103
+ - LinkedIn: https://www.linkedin.com/in/jlianes/
@@ -0,0 +1,77 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "lvbitpack"
7
+ version = "1.0.0"
8
+ description = "LabVIEW Boolean arrays in pure Python: Boolean Array To Number with its sign extension modes, Number To Boolean Array, flattening and bit packing"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ license = "MIT"
12
+ license-files = ["LICENSE"]
13
+ authors = [{ name = "Javier Lianes", email = "javier@lianes8studio.com" }]
14
+ keywords = ["labview", "boolean", "bit", "bitpack", "flatten", "sign-extension", "ni", "binary"]
15
+ classifiers = [
16
+ "Development Status :: 5 - Production/Stable",
17
+ "Intended Audience :: Developers",
18
+ "Intended Audience :: Science/Research",
19
+ "Operating System :: OS Independent",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3 :: Only",
22
+ "Programming Language :: Python :: 3.10",
23
+ "Programming Language :: Python :: 3.11",
24
+ "Programming Language :: Python :: 3.12",
25
+ "Programming Language :: Python :: 3.13",
26
+ "Programming Language :: Python :: 3.14",
27
+ "Topic :: Scientific/Engineering",
28
+ "Topic :: Software Development :: Libraries",
29
+ "Typing :: Typed",
30
+ ]
31
+ dependencies = []
32
+
33
+ [project.optional-dependencies]
34
+ dev = ["ruff", "mypy", "pytest", "build", "twine", "hatchling", "editables"]
35
+
36
+ [project.scripts]
37
+ lvbitpack = "lvbitpack._cli:main"
38
+
39
+ [project.urls]
40
+ Homepage = "https://lianes8studio.com"
41
+ Source = "https://github.com/JaviLianes8/lvbitpack"
42
+ Issues = "https://github.com/JaviLianes8/lvbitpack/issues"
43
+ Changelog = "https://github.com/JaviLianes8/lvbitpack/blob/main/CHANGELOG.md"
44
+ LinkedIn = "https://www.linkedin.com/in/jlianes/"
45
+ Funding = "https://buymeacoffee.com/jlianesglrs"
46
+
47
+ [tool.hatch.build.targets.wheel]
48
+ packages = ["src/lvbitpack"]
49
+
50
+ [tool.hatch.build.targets.sdist]
51
+ include = [
52
+ "src",
53
+ "tests",
54
+ "README.md",
55
+ "GUIDE.md",
56
+ "CHANGELOG.md",
57
+ "CONTRIBUTING.md",
58
+ "LICENSE",
59
+ "pyproject.toml",
60
+ "requirements-dev.txt",
61
+ ]
62
+
63
+ [tool.ruff]
64
+ line-length = 100
65
+ target-version = "py310"
66
+
67
+ [tool.ruff.lint]
68
+ select = ["E", "F", "I", "UP", "B", "SIM", "RUF"]
69
+
70
+ [tool.mypy]
71
+ python_version = "3.10"
72
+ strict = true
73
+
74
+ [tool.pytest.ini_options]
75
+ testpaths = ["tests"]
76
+ pythonpath = ["src"]
77
+ addopts = "-q"
@@ -0,0 +1,3 @@
1
+ # Development dependencies: the package in editable mode plus its dev extras
2
+ # (ruff, mypy, pytest, build, twine). Install with: pip install -r requirements-dev.txt
3
+ -e .[dev]