Skip to content

Commit 52ce715

Browse files
committed
Merge branch 'master' into numpy-dep
2 parents 0e8ec39 + 5bc9128 commit 52ce715

3 files changed

Lines changed: 39 additions & 25 deletions

File tree

pgvector/bit.py

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,39 @@
22
from struct import pack, unpack_from
33
from warnings import warn
44

5+
try:
6+
import numpy as np
7+
except ImportError:
8+
np = None
9+
510

611
class Bit:
712
def __init__(self, value: bytes | str | list[bool] | np.ndarray[tuple[int], np.dtype[np.bool | np.uint8]]) -> None:
813
if isinstance(value, bytes):
914
length = 8 * len(value)
1015
data = value
1116
else:
12-
import numpy as np
17+
if isinstance(value, list):
18+
def bit_value(v: bool) -> str:
19+
if v is True:
20+
return '1'
21+
if v is False:
22+
return '0'
23+
raise ValueError('expected list[bool]')
24+
25+
value = ''.join([bit_value(v) for v in value])
1326

1427
if isinstance(value, str):
15-
value = [v != '0' for v in value]
16-
else:
17-
value = np.asarray(value)
28+
length = len(value)
1829

19-
# for mypy
20-
assert isinstance(value, np.ndarray)
30+
if length % 8 != 0:
31+
value += '0' * (8 - (length % 8))
2132

33+
try:
34+
data = int(value, 2).to_bytes(len(value) // 8, byteorder='big')
35+
except ValueError:
36+
raise ValueError('expected bit string')
37+
elif np is not None and isinstance(value, np.ndarray):
2238
if value.dtype != np.bool:
2339
# skip warning for result of np.unpackbits
2440
if value.dtype != np.uint8 or np.any(value > 1):
@@ -31,8 +47,10 @@ def __init__(self, value: bytes | str | list[bool] | np.ndarray[tuple[int], np.d
3147
if value.ndim != 1:
3248
raise ValueError('expected ndim to be 1')
3349

34-
length = len(value)
35-
data = np.packbits(value).tobytes()
50+
length = len(value)
51+
data = np.packbits(value).tobytes()
52+
else:
53+
raise ValueError('expected bytes, str, list, or ndarray')
3654

3755
self._value = pack('>i', length) + data
3856

tests/test_bit.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,27 @@
88

99

1010
class TestBit:
11-
@pytest.mark.skipif(np is None, reason='NumPy required')
1211
def test_list(self):
1312
assert Bit([True, False, True]).to_list() == [True, False, True]
1413

15-
@pytest.mark.skipif(np is None, reason='NumPy required')
1614
def test_list_none(self):
17-
with pytest.warns(UserWarning, match='expected elements to be boolean'):
18-
assert Bit([True, None, True]).to_text() == '101' # ty: ignore[invalid-argument-type]
15+
with pytest.raises(ValueError) as error:
16+
Bit([True, None, True]) # ty: ignore[invalid-argument-type]
17+
assert str(error.value) == 'expected list[bool]'
1918

20-
@pytest.mark.skipif(np is None, reason='NumPy required')
2119
def test_list_int(self):
22-
with pytest.warns(UserWarning, match='expected elements to be boolean'):
23-
assert Bit([254, 7, 0]).to_text() == '110' # ty: ignore[invalid-argument-type]
20+
with pytest.raises(ValueError) as error:
21+
Bit([254, 7, 0]) # ty: ignore[invalid-argument-type]
22+
assert str(error.value) == 'expected list[bool]'
2423

25-
@pytest.mark.skipif(np is None, reason='NumPy required')
2624
def test_str(self):
2725
assert Bit('101').to_list() == [True, False, True]
2826

27+
def test_str_two(self):
28+
with pytest.raises(ValueError) as error:
29+
Bit('201')
30+
assert str(error.value) == 'expected bit string'
31+
2932
def test_bytes(self):
3033
assert Bit(b'\xff\x00\xf0').to_text() == '111111110000000011110000'
3134
assert Bit(b'\xfe\x07\x00').to_text() == '111111100000011100000000'
@@ -53,24 +56,20 @@ def test_ndarray_uint16(self):
5356
with pytest.warns(UserWarning, match='expected elements to be boolean'):
5457
assert Bit(arr).to_text() == '110'
5558

56-
@pytest.mark.skipif(np is None, reason='NumPy required')
5759
def test_ndim_two(self):
5860
with pytest.raises(ValueError) as error:
5961
Bit([[True, False], [True, False]]) # ty: ignore[invalid-argument-type]
60-
assert str(error.value) == 'expected ndim to be 1'
62+
assert str(error.value) == 'expected list[bool]'
6163

62-
@pytest.mark.skipif(np is None, reason='NumPy required')
6364
def test_ndim_zero(self):
6465
with pytest.raises(ValueError) as error:
6566
Bit(True) # ty: ignore[invalid-argument-type]
66-
assert str(error.value) == 'expected ndim to be 1'
67+
assert str(error.value) == 'expected bytes, str, list, or ndarray'
6768

68-
@pytest.mark.skipif(np is None, reason='NumPy required')
6969
def test_repr(self):
7070
assert repr(Bit([True, False, True])) == 'Bit(101)'
7171
assert str(Bit([True, False, True])) == 'Bit(101)'
7272

73-
@pytest.mark.skipif(np is None, reason='NumPy required')
7473
def test_equality(self):
7574
assert Bit([True, False, True]) == Bit([True, False, True])
7675
assert Bit([True, False, True]) != Bit([True, False, False])

tests/test_psycopg.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -90,21 +90,18 @@ def test_halfvec_text_format(self):
9090
res = next(conn.execute('SELECT %t::halfvec', (embedding,)))[0]
9191
assert res == HalfVector([1.5, 2, 3])
9292

93-
@pytest.mark.skipif(np is None, reason='NumPy required')
9493
def test_bit(self):
9594
embedding = Bit([True, False, True])
9695
conn.execute('INSERT INTO psycopg_items (binary_embedding) VALUES (%s)', (embedding,))
9796

9897
res = next(conn.execute('SELECT binary_embedding FROM psycopg_items ORDER BY id'))[0]
9998
assert res == '101'
10099

101-
@pytest.mark.skipif(np is None, reason='NumPy required')
102100
def test_bit_binary_format(self):
103101
embedding = Bit([False, True, False, True, False, False, False, False, True])
104102
res = next(conn.execute('SELECT %b::bit(9)', (embedding,), binary=True))[0]
105103
assert repr(Bit.from_binary(res)) == 'Bit(010100001)'
106104

107-
@pytest.mark.skipif(np is None, reason='NumPy required')
108105
def test_bit_text_format(self):
109106
embedding = Bit([False, True, False, True, False, False, False, False, True])
110107
res = next(conn.execute('SELECT %t::bit(9)', (embedding,)))[0]

0 commit comments

Comments
 (0)