Skip to content

Commit 81b118c

Browse files
committed
Changed vector and halfvec types to return list for Django, SQLAlchemy, SQLModel, and Peewee
1 parent dc7e3e2 commit 81b118c

11 files changed

Lines changed: 96 additions & 105 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
## 1.0.0 (unreleased)
22

33
- Added experimental support for type hints
4-
- Changed `vector` type to return `Vector` object instead of NumPy array
4+
- Changed `vector` and `halfvec` types to return list for Django, SQLAlchemy, SQLModel, and Peewee
5+
- Changed `vector` type to return `Vector` object for Psycopg 3, Psycopg 2, asyncpg, and pg8000
56
- Removed `utils` package (use top-level `pgvector` package instead)
67
- Removed re-exported classes (use top-level `pgvector` package instead)
78
- Removed dependency on NumPy

pgvector/django/halfvec.py

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,18 @@ def db_type(self, connection: Any) -> str:
2424
return 'halfvec'
2525
return 'halfvec(%d)' % self.dimensions
2626

27-
def from_db_value(self, value: Any, expression: Any, connection: Any) -> HalfVector | None:
28-
return HalfVector._from_db(value)
27+
def from_db_value(self, value: Any, expression: Any, connection: Any) -> list[float] | None:
28+
value = HalfVector._from_db(value)
29+
return None if value is None else value.to_list()
2930

30-
def to_python(self, value: Any) -> HalfVector | None:
31-
if value is None or isinstance(value, HalfVector):
31+
def to_python(self, value: Any) -> list[float] | None:
32+
if value is None or isinstance(value, list):
3233
return value
33-
elif isinstance(value, str):
34-
return HalfVector.from_text(value)
35-
else:
36-
return HalfVector(value)
34+
if isinstance(value, str):
35+
value = HalfVector.from_text(value)
36+
if not isinstance(value, HalfVector):
37+
value = HalfVector(value)
38+
return value.to_list()
3739

3840
def get_prep_value(self, value: Any) -> str | None:
3941
return HalfVector._to_db(value)
@@ -50,16 +52,7 @@ def formfield(self, form_class: Any = None, choices_form_class: Any = None, **kw
5052
)
5153

5254

53-
class HalfVectorWidget(forms.TextInput):
54-
def format_value(self, value: Any) -> Any:
55-
if isinstance(value, HalfVector):
56-
value = value.to_list()
57-
return super().format_value(value)
58-
59-
6055
class HalfVectorFormField(forms.CharField):
61-
widget = HalfVectorWidget
62-
6356
def to_python(self, value: Any) -> Any:
6457
if isinstance(value, str) and value == '':
6558
return None

pgvector/django/vector.py

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,18 @@ def db_type(self, connection: Any) -> str:
2424
return 'vector'
2525
return 'vector(%d)' % self.dimensions
2626

27-
def from_db_value(self, value: Any, expression: Any, connection: Any) -> Vector | None:
28-
return Vector._from_db(value)
27+
def from_db_value(self, value: Any, expression: Any, connection: Any) -> list[float] | None:
28+
value = Vector._from_db(value)
29+
return None if value is None else value.to_list()
2930

30-
def to_python(self, value: Any) -> Vector | None:
31-
if value is None or isinstance(value, Vector):
31+
def to_python(self, value: Any) -> list[float] | None:
32+
if value is None or isinstance(value, list):
3233
return value
33-
elif isinstance(value, str):
34-
return Vector.from_text(value)
35-
else:
36-
return Vector(value)
34+
if isinstance(value, str):
35+
value = Vector.from_text(value)
36+
if not isinstance(value, Vector):
37+
value = Vector(value)
38+
return value.to_list()
3739

3840
def get_prep_value(self, value: Any) -> str | None:
3941
return Vector._to_db(value)
@@ -50,16 +52,7 @@ def formfield(self, form_class: Any = None, choices_form_class: Any = None, **kw
5052
)
5153

5254

53-
class VectorWidget(forms.TextInput):
54-
def format_value(self, value: Any) -> Any:
55-
if isinstance(value, Vector):
56-
value = value.to_list()
57-
return super().format_value(value)
58-
59-
6055
class VectorFormField(forms.CharField):
61-
widget = VectorWidget
62-
6356
def to_python(self, value: Any) -> Any:
6457
if isinstance(value, str) and value == '':
6558
return None

pgvector/peewee/halfvec.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ def get_modifiers(self) -> list[int] | None:
1616
def db_value(self, value: object) -> str | None:
1717
return HalfVector._to_db(value)
1818

19-
def python_value(self, value: Any) -> HalfVector | None:
20-
return HalfVector._from_db(value)
19+
def python_value(self, value: Any) -> list[float] | None:
20+
value = HalfVector._from_db(value)
21+
return None if value is None else value.to_list()
2122

2223
def _distance(self, op: str, vector: object) -> Expression:
2324
return Expression(lhs=self, op=op, rhs=self.to_value(vector))

pgvector/peewee/vector.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,9 @@ def get_modifiers(self) -> list[int] | None:
1616
def db_value(self, value: object) -> str | None:
1717
return Vector._to_db(value)
1818

19-
def python_value(self, value: Any) -> Vector | None:
20-
return Vector._from_db(value)
19+
def python_value(self, value: Any) -> list[float] | None:
20+
value = Vector._from_db(value)
21+
return None if value is None else value.to_list()
2122

2223
def _distance(self, op: str, vector: object) -> Expression:
2324
return Expression(lhs=self, op=op, rhs=self.to_value(vector))

pgvector/sqlalchemy/halfvec.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ def process(value: Any) -> Any:
3131
return process
3232

3333
def result_processor(self, dialect: Dialect, coltype: Any) -> Any:
34-
def process(value: Any) -> HalfVector | None:
35-
return HalfVector._from_db(value)
34+
def process(value: Any) -> list[float] | None:
35+
value = HalfVector._from_db(value)
36+
return None if value is None else value.to_list()
3637
return process
3738

3839
class Comparator(TypeEngine.Comparator[Any]):

pgvector/sqlalchemy/vector.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,9 @@ def process(value: Any) -> Any:
3131
return process
3232

3333
def result_processor(self, dialect: Dialect, coltype: Any) -> Any:
34-
def process(value: Any) -> Vector | None:
35-
return Vector._from_db(value)
34+
def process(value: Any) -> list[float] | None:
35+
value = Vector._from_db(value)
36+
return None if value is None else value.to_list()
3637
return process
3738

3839
class Comparator(TypeEngine.Comparator[Any]):

tests/test_django.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from math import sqrt
1212
import os
1313
import pgvector.django
14-
from pgvector import HalfVector, SparseVector, Vector
14+
from pgvector import SparseVector
1515
from pgvector.django import VectorExtension, VectorField, HalfVectorField, BitField, SparseVectorField, IvfflatIndex, HnswIndex, L2Distance, MaxInnerProduct, CosineDistance, L1Distance, HammingDistance, JaccardDistance
1616
from unittest import mock
1717

@@ -164,11 +164,11 @@ def setup_method(self) -> None:
164164
def test_vector(self) -> None:
165165
Item(id=1, embedding=[1, 2, 3]).save()
166166
item = Item.objects.get(pk=1) # type: ignore
167-
assert item.embedding == Vector([1, 2, 3])
167+
assert item.embedding == [1, 2, 3]
168168

169169
def test_vector_l2_distance(self) -> None:
170170
create_items()
171-
distance = L2Distance('embedding', Vector([1, 1, 1]))
171+
distance = L2Distance('embedding', [1, 1, 1])
172172
items = Item.objects.annotate(distance=distance).order_by(distance) # type: ignore
173173
assert [v.id for v in items] == [1, 3, 2]
174174
assert [v.distance for v in items] == [0, 1, sqrt(3)]
@@ -210,32 +210,32 @@ def test_vector_l1_distance(self) -> None:
210210
def test_halfvec(self) -> None:
211211
Item(id=1, half_embedding=[1, 2, 3]).save()
212212
item = Item.objects.get(pk=1) # type: ignore
213-
assert item.half_embedding == HalfVector([1, 2, 3])
213+
assert item.half_embedding == [1, 2, 3]
214214

215215
def test_halfvec_l2_distance(self) -> None:
216216
create_items()
217-
distance = L2Distance('half_embedding', HalfVector([1, 1, 1]))
217+
distance = L2Distance('half_embedding', [1, 1, 1])
218218
items = Item.objects.annotate(distance=distance).order_by(distance) # type: ignore
219219
assert [v.id for v in items] == [1, 3, 2]
220220
assert [v.distance for v in items] == [0, 1, sqrt(3)]
221221

222222
def test_halfvec_max_inner_product(self) -> None:
223223
create_items()
224-
distance = MaxInnerProduct('half_embedding', HalfVector([1, 1, 1]))
224+
distance = MaxInnerProduct('half_embedding', [1, 1, 1])
225225
items = Item.objects.annotate(distance=distance).order_by(distance) # type: ignore
226226
assert [v.id for v in items] == [2, 3, 1]
227227
assert [v.distance for v in items] == [-6, -4, -3]
228228

229229
def test_halfvec_cosine_distance(self) -> None:
230230
create_items()
231-
distance = CosineDistance('half_embedding', HalfVector([1, 1, 1]))
231+
distance = CosineDistance('half_embedding', [1, 1, 1])
232232
items = Item.objects.annotate(distance=distance).order_by(distance) # type: ignore
233233
assert [v.id for v in items] == [1, 2, 3]
234234
assert [v.distance for v in items] == [0, 0, 0.05719095841793653]
235235

236236
def test_halfvec_l1_distance(self) -> None:
237237
create_items()
238-
distance = L1Distance('half_embedding', HalfVector([1, 1, 1]))
238+
distance = L1Distance('half_embedding', [1, 1, 1])
239239
items = Item.objects.annotate(distance=distance).order_by(distance) # type: ignore
240240
assert [v.id for v in items] == [1, 3, 2]
241241
assert [v.distance for v in items] == [0, 1, 3]
@@ -304,31 +304,31 @@ def test_vector_avg(self) -> None:
304304
Item(embedding=[1, 2, 3]).save()
305305
Item(embedding=[4, 5, 6]).save()
306306
avg = Item.objects.aggregate(Avg('embedding'))['embedding__avg'] # type: ignore
307-
assert avg == Vector([2.5, 3.5, 4.5])
307+
assert avg == [2.5, 3.5, 4.5]
308308

309309
def test_vector_sum(self) -> None:
310310
sum = Item.objects.aggregate(Sum('embedding'))['embedding__sum'] # type: ignore
311311
assert sum is None
312312
Item(embedding=[1, 2, 3]).save()
313313
Item(embedding=[4, 5, 6]).save()
314314
sum = Item.objects.aggregate(Sum('embedding'))['embedding__sum'] # type: ignore
315-
assert sum == Vector([5, 7, 9])
315+
assert sum == [5, 7, 9]
316316

317317
def test_halfvec_avg(self) -> None:
318318
avg = Item.objects.aggregate(Avg('half_embedding'))['half_embedding__avg'] # type: ignore
319319
assert avg is None
320320
Item(half_embedding=[1, 2, 3]).save()
321321
Item(half_embedding=[4, 5, 6]).save()
322322
avg = Item.objects.aggregate(Avg('half_embedding'))['half_embedding__avg'] # type: ignore
323-
assert avg == HalfVector([2.5, 3.5, 4.5])
323+
assert avg == [2.5, 3.5, 4.5]
324324

325325
def test_halfvec_sum(self) -> None:
326326
sum = Item.objects.aggregate(Sum('half_embedding'))['half_embedding__sum'] # type: ignore
327327
assert sum is None
328328
Item(half_embedding=[1, 2, 3]).save()
329329
Item(half_embedding=[4, 5, 6]).save()
330330
sum = Item.objects.aggregate(Sum('half_embedding'))['half_embedding__sum'] # type: ignore
331-
assert sum == HalfVector([5, 7, 9])
331+
assert sum == [5, 7, 9]
332332

333333
def test_serialization(self) -> None:
334334
create_items()
@@ -358,7 +358,7 @@ def test_vector_form_save(self) -> None:
358358
assert form.has_changed()
359359
assert form.is_valid()
360360
assert form.save()
361-
assert Item.objects.get(pk=1).embedding == Vector([4, 5, 6]) # type: ignore
361+
assert Item.objects.get(pk=1).embedding == [4, 5, 6] # type: ignore
362362

363363
def test_vector_form_save_missing(self) -> None:
364364
Item(id=1).save()
@@ -386,7 +386,7 @@ def test_halfvec_form_save(self) -> None:
386386
assert form.has_changed()
387387
assert form.is_valid()
388388
assert form.save()
389-
assert Item.objects.get(pk=1).half_embedding == HalfVector([4, 5, 6]) # type: ignore
389+
assert Item.objects.get(pk=1).half_embedding == [4, 5, 6] # type: ignore
390390

391391
def test_halfvec_form_save_missing(self) -> None:
392392
Item(id=1).save()
@@ -468,15 +468,15 @@ def test_missing(self) -> None:
468468
assert Item.objects.first().sparse_embedding is None # type: ignore
469469

470470
def test_vector_array(self) -> None:
471-
Item(id=1, embeddings=[Vector([1, 2, 3]), Vector([4, 5, 6])]).save()
471+
Item(id=1, embeddings=[[1, 2, 3], [4, 5, 6]]).save()
472472

473473
with connection.cursor() as cursor:
474474
from pgvector.psycopg import register_vector
475475
register_vector(cursor.connection)
476476

477477
# this fails if the driver does not cast arrays
478478
item = Item.objects.get(pk=1) # type: ignore
479-
assert item.embeddings == [Vector([1, 2, 3]), Vector([4, 5, 6])]
479+
assert item.embeddings == [[1, 2, 3], [4, 5, 6]]
480480

481481
def test_double_array(self) -> None:
482482
Item(id=1, double_embedding=[1, 1, 1]).save()

tests/test_peewee.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from math import sqrt
22
from peewee import Model, PostgresqlDatabase, fn
3-
from pgvector import HalfVector, SparseVector, Vector
3+
from pgvector import SparseVector, Vector
44
from pgvector.peewee import VectorField, HalfVectorField, FixedBitField, SparseVectorField
55
from typing import Any
66

@@ -44,7 +44,7 @@ def setup_method(self) -> None:
4444
def test_vector(self) -> None:
4545
Item.create(id=1, embedding=[1, 2, 3])
4646
item = Item.get_by_id(1)
47-
assert item.embedding == Vector([1, 2, 3])
47+
assert item.embedding == [1, 2, 3]
4848

4949
def test_vector_l2_distance(self) -> None:
5050
create_items()
@@ -77,7 +77,7 @@ def test_vector_l1_distance(self) -> None:
7777
def test_halfvec(self) -> None:
7878
Item.create(id=1, half_embedding=[1, 2, 3])
7979
item = Item.get_by_id(1)
80-
assert item.half_embedding == HalfVector([1, 2, 3])
80+
assert item.half_embedding == [1, 2, 3]
8181

8282
def test_halfvec_l2_distance(self) -> None:
8383
create_items()
@@ -170,31 +170,31 @@ def test_vector_avg(self) -> None:
170170
Item.create(embedding=[1, 2, 3])
171171
Item.create(embedding=[4, 5, 6])
172172
avg = Item.select(fn.avg(Item.embedding).coerce(True)).scalar()
173-
assert avg == Vector([2.5, 3.5, 4.5])
173+
assert avg == [2.5, 3.5, 4.5]
174174

175175
def test_vector_sum(self) -> None:
176176
sum = Item.select(fn.sum(Item.embedding).coerce(True)).scalar()
177177
assert sum is None
178178
Item.create(embedding=[1, 2, 3])
179179
Item.create(embedding=[4, 5, 6])
180180
sum = Item.select(fn.sum(Item.embedding).coerce(True)).scalar()
181-
assert sum == Vector([5, 7, 9])
181+
assert sum == [5, 7, 9]
182182

183183
def test_halfvec_avg(self) -> None:
184184
avg = Item.select(fn.avg(Item.half_embedding).coerce(True)).scalar()
185185
assert avg is None
186186
Item.create(half_embedding=[1, 2, 3])
187187
Item.create(half_embedding=[4, 5, 6])
188188
avg = Item.select(fn.avg(Item.half_embedding).coerce(True)).scalar()
189-
assert avg == HalfVector([2.5, 3.5, 4.5])
189+
assert avg == [2.5, 3.5, 4.5]
190190

191191
def test_halfvec_sum(self) -> None:
192192
sum = Item.select(fn.sum(Item.half_embedding).coerce(True)).scalar()
193193
assert sum is None
194194
Item.create(half_embedding=[1, 2, 3])
195195
Item.create(half_embedding=[4, 5, 6])
196196
sum = Item.select(fn.sum(Item.half_embedding).coerce(True)).scalar()
197-
assert sum == HalfVector([5, 7, 9])
197+
assert sum == [5, 7, 9]
198198

199199
def test_get_or_create(self) -> None:
200200
Item.get_or_create(id=1, defaults={'embedding': [1, 2, 3]})

0 commit comments

Comments
 (0)