Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
d21b56c
Starting on v5.0
scott-griffiths Mar 11, 2026
3825275
Removing more bitarray - replacing with tibs when needed.
scott-griffiths Mar 11, 2026
906411a
Moving the common helpers.
scott-griffiths Mar 11, 2026
732e6eb
Renaming bitstore_tibs_helpers.py to bitstore_helpers.py
scott-griffiths Mar 11, 2026
636df3f
Renaming _bits to tibs.
scott-griffiths Mar 12, 2026
ad7f087
Fix for signed ints > 128 bits not being interpreted correctly.
scott-griffiths Mar 12, 2026
cbd1f8f
Using the constructors instead of from_tibs and from_mutibs methods.
scott-griffiths Mar 13, 2026
b314d75
A few simplifications.
scott-griffiths Mar 14, 2026
59f5dd6
Defaulting more to Mutibs during construction.
scott-griffiths Mar 14, 2026
0efbd29
Fixes for upgrade to tibs 0.6.
Apr 3, 2026
6812800
Removing some validation that's not needed any more as tibs is doing it.
Apr 3, 2026
fbc0236
Better encapsulation of tibs and using more of its methods.
Apr 4, 2026
362f91c
Removing duplicated length check.
Apr 4, 2026
ebaff1c
Factoring out mutibs creation from ints.
Apr 4, 2026
afa1b55
Using tibs.__iter__ directly.
Apr 4, 2026
409a797
Updating to use tibs 0.7.
May 4, 2026
1dc5936
Removing lsb0 mode.
May 5, 2026
49a06f4
Fix to use find_all_iter.
May 5, 2026
fd1a31b
Removing deprecated way of setting module options.
May 5, 2026
cfe15e6
Fix to return type if ilshift and irshift for MutableBitStore.
May 9, 2026
1e0c1b2
Fix for some comparisons raising AttributeError
May 9, 2026
8919cbb
Allowing 0 as a factor when expanding brackets.
May 9, 2026
08720d0
Fix for other correct inplace Array return types.
May 9, 2026
d014786
Scale should be used in Dtype comparisons.
May 9, 2026
fc9ca95
Check for negative factors in tokens.
May 9, 2026
21e5764
Don't let reinitialising the Options reset them.
May 9, 2026
38a20a1
Better array insert clamping.
May 9, 2026
38b0156
A few fixes for Array.fromfile, plus some AI generated unit tests.
May 9, 2026
b295348
Using offset and length of Tibs.from_bytes to improve performance.
May 9, 2026
5b7f144
Don't always read file into memory if offset given.
May 9, 2026
12170fc
Removing deprecated old options.
May 9, 2026
7456cfc
Fix to make ConstBitStore the default. Surprisingly big speed up :)
May 9, 2026
8d7b4de
More ConstBitStore correctness.
May 9, 2026
46ac240
More small performance improvements.
May 9, 2026
528517b
Fast path for cut with a const bitstore.
May 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 0 additions & 95 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -160,100 +160,6 @@ jobs:
ls -la

- name: Run pytest
env:
BITSTRING_USE_RUST_CORE: ${{ matrix.rust.env_var }}
run: |
python -m pytest tests/ --benchmark-disable

test-rust:
name: Test ${{ matrix.os.name }} ${{ matrix.python.name }} (Rust core)
if: always()
needs:
- test-standard
runs-on: ${{ matrix.os.runs-on }}
strategy:
fail-fast: false
matrix:
os:
- name: 🐧
runs-on: ubuntu-latest
- name: 🍎
runs-on: macos-15
- name: 🪟
runs-on: windows-latest
python:
- name: CPython 3.8
major_dot_minor: '3.8'
action: '3.8'
- name: CPython 3.9
major_dot_minor: '3.9'
action: '3.9'
- name: CPython 3.10
major_dot_minor: '3.10'
action: '3.10'
- name: CPython 3.11
major_dot_minor: '3.11'
action: '3.11'
- name: CPython 3.12
major_dot_minor: '3.12'
action: '3.12'
- name: CPython 3.13
major_dot_minor: '3.13'
action: '3.13'
- name: CPython 3.14
major_dot_minor: '3.14'
action: '3.14'
exclude:
- os:
name: "🍎"
runs-on: macos-15
python:
name: "CPython 3.8"
major_dot_minor: '3.8'
action: '3.8'
- os:
name: "🍎"
runs-on: macos-15
python:
name: "CPython 3.9"
major_dot_minor: '3.9'
action: '3.9'
- os:
name: "🍎"
runs-on: macos-15
python:
name: "CPython 3.10"
major_dot_minor: '3.10'
action: '3.10'
steps:
- uses: actions/checkout@v4
with:
path: repo

- name: Download package files
uses: actions/download-artifact@v4
with:
name: packages
path: dist

- uses: actions/setup-python@v5
with:
python-version: ${{ fromJSON(format('["{0}", "{1}"]', format('{0}.0-alpha - {0}.X', matrix.python.action), matrix.python.action))[startsWith(matrix.python.action, 'pypy')] }}
architecture: x64

- name: Setup environment
run: |
python --version --version
# make sure we test the installed code
cp -R repo/tests/ tests/
python -m pip install -r tests/requirements.txt
python -m pip install ./dist/*.whl
# show the directory contents for diagnostics
ls -la

- name: Run pytest
env:
BITSTRING_USE_RUST_CORE: '1'
run: |
python -m pytest tests/ --benchmark-disable

Expand All @@ -268,7 +174,6 @@ jobs:
# a merge.
- build
- test-standard
- test-rust
steps:
- name: Require all successes
uses: re-actors/alls-green@v1.2.2
Expand Down
51 changes: 3 additions & 48 deletions bitstring/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,26 +55,12 @@
THE SOFTWARE.
"""

__version__ = "4.4.0"
__version__ = "5.0.0"

__author__ = "Scott Griffiths"

import sys
import os
import importlib

# New ability to use tibs for core operations instead of bitarray.
# Tibs is written in Rust and is still in beta. Use the environment variable
# BITSTRING_USE_RUST_CORE=1 before importing the module to turn it on.
_env_core = os.getenv('BITSTRING_USE_RUST_CORE', '').strip().lower()
_USE_RUST_CORE = _env_core in ('1', 'true', 'yes', 'on')
if _USE_RUST_CORE:
bitstore = importlib.import_module('bitstring.bitstore_tibs')
bitstore_helpers = importlib.import_module('bitstring.bitstore_tibs_helpers')
else:
bitstore = importlib.import_module('bitstring.bitstore_bitarray')
bitstore_helpers = importlib.import_module('bitstring.bitstore_bitarray_helpers')
bitstore_common_helpers = importlib.import_module('bitstring.bitstore_common_helpers')
import bitstring.bitstore_tibs as bitstore

from .bits import Bits
from .bitstring_options import Options
Expand All @@ -84,7 +70,6 @@
from .array_ import Array
from .exceptions import Error, ReadError, InterpretError, ByteAlignError, CreationError
from .dtypes import DtypeDefinition, dtype_register, Dtype
import types
from typing import List, Tuple, Literal
from .mxfp import decompress_luts as mxfp_decompress_luts
from .fp8 import decompress_luts as binary8_decompress_luts
Expand All @@ -96,36 +81,6 @@
# The Options class returns a singleton.
options = Options()

# These get defined properly by the module magic below. This just stops mypy complaining about them.
bytealigned = lsb0 = None


# An opaque way of adding module level properties. Taken from https://peps.python.org/pep-0549/
# This is now deprecated. Use the options object directly instead.
class _MyModuleType(types.ModuleType):
@property
def bytealigned(self) -> bool:
"""Determines whether a number of methods default to working only on byte boundaries."""
return options.bytealigned

@bytealigned.setter
def bytealigned(self, value: bool) -> None:
"""Determines whether a number of methods default to working only on byte boundaries."""
options.bytealigned = value

@property
def lsb0(self) -> bool:
"""If True, the least significant bit (the final bit) is indexed as bit zero."""
return options.lsb0

@lsb0.setter
def lsb0(self, value: bool) -> None:
"""If True, the least significant bit (the final bit) is indexed as bit zero."""
options.lsb0 = value


sys.modules[__name__].__class__ = _MyModuleType


# These methods convert a bit length to the number of characters needed to print it for different interpretations.
def hex_bits2chars(bitlength: int):
Expand Down Expand Up @@ -349,4 +304,4 @@ def bool_bits2chars(_: Literal[1]):

__all__ = ['ConstBitStream', 'BitStream', 'BitArray', 'Array',
'Bits', 'pack', 'Error', 'ReadError', 'InterpretError',
'ByteAlignError', 'CreationError', 'bytealigned', 'lsb0', 'Dtype', 'options']
'ByteAlignError', 'CreationError', 'Dtype', 'options']
44 changes: 30 additions & 14 deletions bitstring/array_.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,8 @@ def insert(self, i: int, x: ElementType) -> None:
"""Insert a new element into the Array at position i.

"""
i = min(i, len(self)) # Inserting beyond len of array inserts at the end (copying standard behaviour)
# Match list.insert semantics: clamp both high and low indices.
i = max(min(i, len(self)), -len(self))
self.data.insert(self._create_element(x), i * self._dtype.length)

def pop(self, i: int = -1) -> ElementType:
Expand Down Expand Up @@ -370,11 +371,19 @@ def fromfile(self, f: BinaryIO, n: Optional[int] = None) -> None:
trailing_bit_length = len(self.data) % self._dtype.bitlength
if trailing_bit_length != 0:
raise ValueError(f"Cannot extend Array as its data length ({len(self.data)} bits) is not a multiple of the format length ({self._dtype.bitlength} bits).")
if n is not None and n < 0:
raise ValueError("n must be >= 0.")

new_data = Bits(f)
max_items = len(new_data) // self._dtype.length
item_bits = self._dtype.bitlength
if n is None:
b = f.read()
else:
bytes_needed = (n * item_bits + 7) // 8
b = f.read(bytes_needed)
new_data = Bits(b)
max_items = len(new_data) // item_bits
items_to_append = max_items if n is None else min(n, max_items)
self.data += new_data[0: items_to_append * self._dtype.bitlength]
self.data += new_data[0: items_to_append * item_bits]
if n is not None and items_to_append < n:
raise EOFError(f"Only {items_to_append} were appended, not the {n} items requested.")

Expand Down Expand Up @@ -439,7 +448,7 @@ def pp(self, fmt: Optional[str] = None, width: int = 120,
length = len(self.data) // token_length
len_str = colour.green + str(length) + colour.off
stream.write(f"<{self.__class__.__name__} {tidy_fmt}, length={len_str}, itemsize={token_length} bits, total data size={(len(self.data) + 7) // 8} bytes> [\n")
data._pp(dtype1, dtype2, token_length, width, sep, format_sep, show_offset, stream, False, token_length)
data._pp(dtype1, dtype2, token_length, width, sep, format_sep, show_offset, stream, token_length)
stream.write("]")
if trailing_bit_length != 0:
stream.write(" + trailing_bits = " + str(self.data[-trailing_bit_length:]))
Expand Down Expand Up @@ -575,6 +584,13 @@ def _apply_op_between_arrays(self, op, other: Array, is_comparison: bool = False
new_array.data = new_data
return new_array

def _apply_op_between_arrays_inplace(self, op, other: Array) -> Array:
"""Apply op between Arrays and update self in place."""
result = self._apply_op_between_arrays(op, other)
self._dtype = result._dtype
self.data = result.data
return self

@classmethod
def _promotetype(cls, type1: Dtype, type2: Dtype) -> Dtype:
"""When combining types which one wins?
Expand Down Expand Up @@ -618,12 +634,12 @@ def __add__(self, other: Union[int, float, Array]) -> Array:

def __iadd__(self, other: Union[int, float, Array]) -> Array:
if isinstance(other, Array):
return self._apply_op_between_arrays(operator.add, other)
return self._apply_op_between_arrays_inplace(operator.add, other)
return self._apply_op_to_all_elements_inplace(operator.add, other)

def __isub__(self, other: Union[int, float, Array]) -> Array:
if isinstance(other, Array):
return self._apply_op_between_arrays(operator.sub, other)
return self._apply_op_between_arrays_inplace(operator.sub, other)
return self._apply_op_to_all_elements_inplace(operator.sub, other)

def __sub__(self, other: Union[int, float, Array]) -> Array:
Expand All @@ -638,7 +654,7 @@ def __mul__(self, other: Union[int, float, Array]) -> Array:

def __imul__(self, other: Union[int, float, Array]) -> Array:
if isinstance(other, Array):
return self._apply_op_between_arrays(operator.mul, other)
return self._apply_op_between_arrays_inplace(operator.mul, other)
return self._apply_op_to_all_elements_inplace(operator.mul, other)

def __floordiv__(self, other: Union[int, float, Array]) -> Array:
Expand All @@ -648,7 +664,7 @@ def __floordiv__(self, other: Union[int, float, Array]) -> Array:

def __ifloordiv__(self, other: Union[int, float, Array]) -> Array:
if isinstance(other, Array):
return self._apply_op_between_arrays(operator.floordiv, other)
return self._apply_op_between_arrays_inplace(operator.floordiv, other)
return self._apply_op_to_all_elements_inplace(operator.floordiv, other)

def __truediv__(self, other: Union[int, float, Array]) -> Array:
Expand All @@ -658,7 +674,7 @@ def __truediv__(self, other: Union[int, float, Array]) -> Array:

def __itruediv__(self, other: Union[int, float, Array]) -> Array:
if isinstance(other, Array):
return self._apply_op_between_arrays(operator.truediv, other)
return self._apply_op_between_arrays_inplace(operator.truediv, other)
return self._apply_op_to_all_elements_inplace(operator.truediv, other)

def __rshift__(self, other: Union[int, Array]) -> Array:
Expand All @@ -673,12 +689,12 @@ def __lshift__(self, other: Union[int, Array]) -> Array:

def __irshift__(self, other: Union[int, Array]) -> Array:
if isinstance(other, Array):
return self._apply_op_between_arrays(operator.rshift, other)
return self._apply_op_between_arrays_inplace(operator.rshift, other)
return self._apply_op_to_all_elements_inplace(operator.rshift, other)

def __ilshift__(self, other: Union[int, Array]) -> Array:
if isinstance(other, Array):
return self._apply_op_between_arrays(operator.lshift, other)
return self._apply_op_between_arrays_inplace(operator.lshift, other)
return self._apply_op_to_all_elements_inplace(operator.lshift, other)

def __mod__(self, other: Union[int, Array]) -> Array:
Expand All @@ -688,7 +704,7 @@ def __mod__(self, other: Union[int, Array]) -> Array:

def __imod__(self, other: Union[int, Array]) -> Array:
if isinstance(other, Array):
return self._apply_op_between_arrays(operator.mod, other)
return self._apply_op_between_arrays_inplace(operator.mod, other)
return self._apply_op_to_all_elements_inplace(operator.mod, other)

# Bitwise operators
Expand Down Expand Up @@ -775,4 +791,4 @@ def __neg__(self):
return self._apply_op_to_all_elements(operator.neg, None)

def __abs__(self):
return self._apply_op_to_all_elements(operator.abs, None)
return self._apply_op_to_all_elements(operator.abs, None)
Loading
Loading