pgvector 0.3.5__py3-none-any.whl

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 (42) hide show
  1. pgvector/asyncpg/__init__.py +9 -0
  2. pgvector/asyncpg/register.py +31 -0
  3. pgvector/django/__init__.py +26 -0
  4. pgvector/django/bit.py +32 -0
  5. pgvector/django/extensions.py +6 -0
  6. pgvector/django/functions.py +55 -0
  7. pgvector/django/halfvec.py +60 -0
  8. pgvector/django/indexes.py +46 -0
  9. pgvector/django/sparsevec.py +55 -0
  10. pgvector/django/vector.py +73 -0
  11. pgvector/peewee/__init__.py +14 -0
  12. pgvector/peewee/bit.py +21 -0
  13. pgvector/peewee/halfvec.py +34 -0
  14. pgvector/peewee/sparsevec.py +34 -0
  15. pgvector/peewee/vector.py +34 -0
  16. pgvector/psycopg/__init__.py +11 -0
  17. pgvector/psycopg/bit.py +31 -0
  18. pgvector/psycopg/halfvec.py +53 -0
  19. pgvector/psycopg/register.py +37 -0
  20. pgvector/psycopg/sparsevec.py +53 -0
  21. pgvector/psycopg/vector.py +58 -0
  22. pgvector/psycopg2/__init__.py +8 -0
  23. pgvector/psycopg2/halfvec.py +20 -0
  24. pgvector/psycopg2/register.py +28 -0
  25. pgvector/psycopg2/sparsevec.py +20 -0
  26. pgvector/psycopg2/vector.py +21 -0
  27. pgvector/sqlalchemy/__init__.py +19 -0
  28. pgvector/sqlalchemy/bit.py +26 -0
  29. pgvector/sqlalchemy/functions.py +14 -0
  30. pgvector/sqlalchemy/halfvec.py +51 -0
  31. pgvector/sqlalchemy/sparsevec.py +51 -0
  32. pgvector/sqlalchemy/vector.py +51 -0
  33. pgvector/utils/__init__.py +11 -0
  34. pgvector/utils/bit.py +61 -0
  35. pgvector/utils/halfvec.py +78 -0
  36. pgvector/utils/sparsevec.py +156 -0
  37. pgvector/utils/vector.py +78 -0
  38. pgvector-0.3.5.dist-info/LICENSE.txt +21 -0
  39. pgvector-0.3.5.dist-info/METADATA +554 -0
  40. pgvector-0.3.5.dist-info/RECORD +42 -0
  41. pgvector-0.3.5.dist-info/WHEEL +5 -0
  42. pgvector-0.3.5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,53 @@
1
+ from psycopg.adapt import Loader, Dumper
2
+ from psycopg.pq import Format
3
+ from ..utils import SparseVector
4
+
5
+
6
+ class SparseVectorDumper(Dumper):
7
+
8
+ format = Format.TEXT
9
+
10
+ def dump(self, obj):
11
+ return SparseVector._to_db(obj).encode('utf8')
12
+
13
+
14
+ class SparseVectorBinaryDumper(SparseVectorDumper):
15
+
16
+ format = Format.BINARY
17
+
18
+ def dump(self, obj):
19
+ return SparseVector._to_db_binary(obj)
20
+
21
+
22
+ class SparseVectorLoader(Loader):
23
+
24
+ format = Format.TEXT
25
+
26
+ def load(self, data):
27
+ if isinstance(data, memoryview):
28
+ data = bytes(data)
29
+ return SparseVector._from_db(data.decode('utf8'))
30
+
31
+
32
+ class SparseVectorBinaryLoader(SparseVectorLoader):
33
+
34
+ format = Format.BINARY
35
+
36
+ def load(self, data):
37
+ if isinstance(data, memoryview):
38
+ data = bytes(data)
39
+ return SparseVector._from_db_binary(data)
40
+
41
+
42
+ def register_sparsevec_info(context, info):
43
+ info.register(context)
44
+
45
+ # add oid to anonymous class for set_types
46
+ text_dumper = type('', (SparseVectorDumper,), {'oid': info.oid})
47
+ binary_dumper = type('', (SparseVectorBinaryDumper,), {'oid': info.oid})
48
+
49
+ adapters = context.adapters
50
+ adapters.register_dumper(SparseVector, text_dumper)
51
+ adapters.register_dumper(SparseVector, binary_dumper)
52
+ adapters.register_loader(info.oid, SparseVectorLoader)
53
+ adapters.register_loader(info.oid, SparseVectorBinaryLoader)
@@ -0,0 +1,58 @@
1
+ import psycopg
2
+ from psycopg.adapt import Loader, Dumper
3
+ from psycopg.pq import Format
4
+ from ..utils import Vector
5
+
6
+
7
+ class VectorDumper(Dumper):
8
+
9
+ format = Format.TEXT
10
+
11
+ def dump(self, obj):
12
+ return Vector._to_db(obj).encode('utf8')
13
+
14
+
15
+ class VectorBinaryDumper(VectorDumper):
16
+
17
+ format = Format.BINARY
18
+
19
+ def dump(self, obj):
20
+ return Vector._to_db_binary(obj)
21
+
22
+
23
+ class VectorLoader(Loader):
24
+
25
+ format = Format.TEXT
26
+
27
+ def load(self, data):
28
+ if isinstance(data, memoryview):
29
+ data = bytes(data)
30
+ return Vector._from_db(data.decode('utf8'))
31
+
32
+
33
+ class VectorBinaryLoader(VectorLoader):
34
+
35
+ format = Format.BINARY
36
+
37
+ def load(self, data):
38
+ if isinstance(data, memoryview):
39
+ data = bytes(data)
40
+ return Vector._from_db_binary(data)
41
+
42
+
43
+ def register_vector_info(context, info):
44
+ if info is None:
45
+ raise psycopg.ProgrammingError('vector type not found in the database')
46
+ info.register(context)
47
+
48
+ # add oid to anonymous class for set_types
49
+ text_dumper = type('', (VectorDumper,), {'oid': info.oid})
50
+ binary_dumper = type('', (VectorBinaryDumper,), {'oid': info.oid})
51
+
52
+ adapters = context.adapters
53
+ adapters.register_dumper('numpy.ndarray', text_dumper)
54
+ adapters.register_dumper('numpy.ndarray', binary_dumper)
55
+ adapters.register_dumper(Vector, text_dumper)
56
+ adapters.register_dumper(Vector, binary_dumper)
57
+ adapters.register_loader(info.oid, VectorLoader)
58
+ adapters.register_loader(info.oid, VectorBinaryLoader)
@@ -0,0 +1,8 @@
1
+ from .register import register_vector
2
+ from ..utils import HalfVector, SparseVector
3
+
4
+ __all__ = [
5
+ 'register_vector',
6
+ 'HalfVector',
7
+ 'SparseVector'
8
+ ]
@@ -0,0 +1,20 @@
1
+ from psycopg2.extensions import adapt, new_type, register_adapter, register_type
2
+ from ..utils import HalfVector
3
+
4
+
5
+ class HalfvecAdapter:
6
+ def __init__(self, value):
7
+ self._value = value
8
+
9
+ def getquoted(self):
10
+ return adapt(HalfVector._to_db(self._value)).getquoted()
11
+
12
+
13
+ def cast_halfvec(value, cur):
14
+ return HalfVector._from_db(value)
15
+
16
+
17
+ def register_halfvec_info(oid, scope):
18
+ halfvec = new_type((oid,), 'HALFVEC', cast_halfvec)
19
+ register_type(halfvec, scope)
20
+ register_adapter(HalfVector, HalfvecAdapter)
@@ -0,0 +1,28 @@
1
+ import psycopg2
2
+ from psycopg2.extensions import cursor
3
+ from .halfvec import register_halfvec_info
4
+ from .sparsevec import register_sparsevec_info
5
+ from .vector import register_vector_info
6
+
7
+
8
+ # TODO make globally False by default in 0.4.0
9
+ # note: register_adapter is always global
10
+ def register_vector(conn_or_curs=None, globally=True):
11
+ conn = conn_or_curs if hasattr(conn_or_curs, 'cursor') else conn_or_curs.connection
12
+ cur = conn.cursor(cursor_factory=cursor)
13
+ scope = None if globally else conn_or_curs
14
+
15
+ # use to_regtype to get first matching type in search path
16
+ cur.execute("SELECT typname, oid FROM pg_type WHERE oid IN (to_regtype('vector'), to_regtype('halfvec'), to_regtype('sparsevec'))")
17
+ type_info = dict(cur.fetchall())
18
+
19
+ if 'vector' not in type_info:
20
+ raise psycopg2.ProgrammingError('vector type not found in the database')
21
+
22
+ register_vector_info(type_info['vector'], scope)
23
+
24
+ if 'halfvec' in type_info:
25
+ register_halfvec_info(type_info['halfvec'], scope)
26
+
27
+ if 'sparsevec' in type_info:
28
+ register_sparsevec_info(type_info['sparsevec'], scope)
@@ -0,0 +1,20 @@
1
+ from psycopg2.extensions import adapt, new_type, register_adapter, register_type
2
+ from ..utils import SparseVector
3
+
4
+
5
+ class SparsevecAdapter:
6
+ def __init__(self, value):
7
+ self._value = value
8
+
9
+ def getquoted(self):
10
+ return adapt(SparseVector._to_db(self._value)).getquoted()
11
+
12
+
13
+ def cast_sparsevec(value, cur):
14
+ return SparseVector._from_db(value)
15
+
16
+
17
+ def register_sparsevec_info(oid, scope):
18
+ sparsevec = new_type((oid,), 'SPARSEVEC', cast_sparsevec)
19
+ register_type(sparsevec, scope)
20
+ register_adapter(SparseVector, SparsevecAdapter)
@@ -0,0 +1,21 @@
1
+ import numpy as np
2
+ from psycopg2.extensions import adapt, new_type, register_adapter, register_type
3
+ from ..utils import Vector
4
+
5
+
6
+ class VectorAdapter:
7
+ def __init__(self, value):
8
+ self._value = value
9
+
10
+ def getquoted(self):
11
+ return adapt(Vector._to_db(self._value)).getquoted()
12
+
13
+
14
+ def cast_vector(value, cur):
15
+ return Vector._from_db(value)
16
+
17
+
18
+ def register_vector_info(oid, scope):
19
+ vector = new_type((oid,), 'VECTOR', cast_vector)
20
+ register_type(vector, scope)
21
+ register_adapter(np.ndarray, VectorAdapter)
@@ -0,0 +1,19 @@
1
+ from .bit import BIT
2
+ from .functions import avg, sum
3
+ from .halfvec import HALFVEC
4
+ from .sparsevec import SPARSEVEC
5
+ from .vector import VECTOR
6
+ from .vector import VECTOR as Vector
7
+ from ..utils import HalfVector, SparseVector
8
+
9
+ __all__ = [
10
+ 'Vector',
11
+ 'VECTOR',
12
+ 'HALFVEC',
13
+ 'BIT',
14
+ 'SPARSEVEC',
15
+ 'HalfVector',
16
+ 'SparseVector',
17
+ 'avg',
18
+ 'sum'
19
+ ]
@@ -0,0 +1,26 @@
1
+ from sqlalchemy.dialects.postgresql.base import ischema_names
2
+ from sqlalchemy.types import UserDefinedType, Float
3
+
4
+
5
+ class BIT(UserDefinedType):
6
+ cache_ok = True
7
+
8
+ def __init__(self, length=None):
9
+ super(UserDefinedType, self).__init__()
10
+ self.length = length
11
+
12
+ def get_col_spec(self, **kw):
13
+ if self.length is None:
14
+ return 'BIT'
15
+ return 'BIT(%d)' % self.length
16
+
17
+ class comparator_factory(UserDefinedType.Comparator):
18
+ def hamming_distance(self, other):
19
+ return self.op('<~>', return_type=Float)(other)
20
+
21
+ def jaccard_distance(self, other):
22
+ return self.op('<%>', return_type=Float)(other)
23
+
24
+
25
+ # for reflection
26
+ ischema_names['bit'] = BIT
@@ -0,0 +1,14 @@
1
+ # https://docs.sqlalchemy.org/en/20/core/functions.html
2
+ # include sum for a consistent API
3
+ from sqlalchemy.sql.functions import ReturnTypeFromArgs, sum
4
+
5
+
6
+ class avg(ReturnTypeFromArgs):
7
+ inherit_cache = True
8
+ package = 'pgvector'
9
+
10
+
11
+ __all__ = [
12
+ 'avg',
13
+ 'sum'
14
+ ]
@@ -0,0 +1,51 @@
1
+ from sqlalchemy.dialects.postgresql.base import ischema_names
2
+ from sqlalchemy.types import UserDefinedType, Float, String
3
+ from ..utils import HalfVector
4
+
5
+
6
+ class HALFVEC(UserDefinedType):
7
+ cache_ok = True
8
+ _string = String()
9
+
10
+ def __init__(self, dim=None):
11
+ super(UserDefinedType, self).__init__()
12
+ self.dim = dim
13
+
14
+ def get_col_spec(self, **kw):
15
+ if self.dim is None:
16
+ return 'HALFVEC'
17
+ return 'HALFVEC(%d)' % self.dim
18
+
19
+ def bind_processor(self, dialect):
20
+ def process(value):
21
+ return HalfVector._to_db(value, self.dim)
22
+ return process
23
+
24
+ def literal_processor(self, dialect):
25
+ string_literal_processor = self._string._cached_literal_processor(dialect)
26
+
27
+ def process(value):
28
+ return string_literal_processor(HalfVector._to_db(value, self.dim))
29
+ return process
30
+
31
+ def result_processor(self, dialect, coltype):
32
+ def process(value):
33
+ return HalfVector._from_db(value)
34
+ return process
35
+
36
+ class comparator_factory(UserDefinedType.Comparator):
37
+ def l2_distance(self, other):
38
+ return self.op('<->', return_type=Float)(other)
39
+
40
+ def max_inner_product(self, other):
41
+ return self.op('<#>', return_type=Float)(other)
42
+
43
+ def cosine_distance(self, other):
44
+ return self.op('<=>', return_type=Float)(other)
45
+
46
+ def l1_distance(self, other):
47
+ return self.op('<+>', return_type=Float)(other)
48
+
49
+
50
+ # for reflection
51
+ ischema_names['halfvec'] = HALFVEC
@@ -0,0 +1,51 @@
1
+ from sqlalchemy.dialects.postgresql.base import ischema_names
2
+ from sqlalchemy.types import UserDefinedType, Float, String
3
+ from ..utils import SparseVector
4
+
5
+
6
+ class SPARSEVEC(UserDefinedType):
7
+ cache_ok = True
8
+ _string = String()
9
+
10
+ def __init__(self, dim=None):
11
+ super(UserDefinedType, self).__init__()
12
+ self.dim = dim
13
+
14
+ def get_col_spec(self, **kw):
15
+ if self.dim is None:
16
+ return 'SPARSEVEC'
17
+ return 'SPARSEVEC(%d)' % self.dim
18
+
19
+ def bind_processor(self, dialect):
20
+ def process(value):
21
+ return SparseVector._to_db(value, self.dim)
22
+ return process
23
+
24
+ def literal_processor(self, dialect):
25
+ string_literal_processor = self._string._cached_literal_processor(dialect)
26
+
27
+ def process(value):
28
+ return string_literal_processor(SparseVector._to_db(value, self.dim))
29
+ return process
30
+
31
+ def result_processor(self, dialect, coltype):
32
+ def process(value):
33
+ return SparseVector._from_db(value)
34
+ return process
35
+
36
+ class comparator_factory(UserDefinedType.Comparator):
37
+ def l2_distance(self, other):
38
+ return self.op('<->', return_type=Float)(other)
39
+
40
+ def max_inner_product(self, other):
41
+ return self.op('<#>', return_type=Float)(other)
42
+
43
+ def cosine_distance(self, other):
44
+ return self.op('<=>', return_type=Float)(other)
45
+
46
+ def l1_distance(self, other):
47
+ return self.op('<+>', return_type=Float)(other)
48
+
49
+
50
+ # for reflection
51
+ ischema_names['sparsevec'] = SPARSEVEC
@@ -0,0 +1,51 @@
1
+ from sqlalchemy.dialects.postgresql.base import ischema_names
2
+ from sqlalchemy.types import UserDefinedType, Float, String
3
+ from ..utils import Vector
4
+
5
+
6
+ class VECTOR(UserDefinedType):
7
+ cache_ok = True
8
+ _string = String()
9
+
10
+ def __init__(self, dim=None):
11
+ super(UserDefinedType, self).__init__()
12
+ self.dim = dim
13
+
14
+ def get_col_spec(self, **kw):
15
+ if self.dim is None:
16
+ return 'VECTOR'
17
+ return 'VECTOR(%d)' % self.dim
18
+
19
+ def bind_processor(self, dialect):
20
+ def process(value):
21
+ return Vector._to_db(value, self.dim)
22
+ return process
23
+
24
+ def literal_processor(self, dialect):
25
+ string_literal_processor = self._string._cached_literal_processor(dialect)
26
+
27
+ def process(value):
28
+ return string_literal_processor(Vector._to_db(value, self.dim))
29
+ return process
30
+
31
+ def result_processor(self, dialect, coltype):
32
+ def process(value):
33
+ return Vector._from_db(value)
34
+ return process
35
+
36
+ class comparator_factory(UserDefinedType.Comparator):
37
+ def l2_distance(self, other):
38
+ return self.op('<->', return_type=Float)(other)
39
+
40
+ def max_inner_product(self, other):
41
+ return self.op('<#>', return_type=Float)(other)
42
+
43
+ def cosine_distance(self, other):
44
+ return self.op('<=>', return_type=Float)(other)
45
+
46
+ def l1_distance(self, other):
47
+ return self.op('<+>', return_type=Float)(other)
48
+
49
+
50
+ # for reflection
51
+ ischema_names['vector'] = VECTOR
@@ -0,0 +1,11 @@
1
+ from .bit import Bit
2
+ from .halfvec import HalfVector
3
+ from .sparsevec import SparseVector
4
+ from .vector import Vector
5
+
6
+ __all__ = [
7
+ 'Vector',
8
+ 'HalfVector',
9
+ 'Bit',
10
+ 'SparseVector'
11
+ ]
pgvector/utils/bit.py ADDED
@@ -0,0 +1,61 @@
1
+ import numpy as np
2
+ from struct import pack, unpack_from
3
+
4
+
5
+ class Bit:
6
+ def __init__(self, value):
7
+ if isinstance(value, str):
8
+ self._value = self.from_text(value)._value
9
+ else:
10
+ # TODO change in 0.4.0
11
+ # TODO raise if dtype not bool or uint8
12
+ # if isinstance(value, np.ndarray) and value.dtype == np.uint8:
13
+ # value = np.unpackbits(value)
14
+ # else:
15
+ # value = np.asarray(value, dtype=bool)
16
+
17
+ value = np.asarray(value, dtype=bool)
18
+
19
+ if value.ndim != 1:
20
+ raise ValueError('expected ndim to be 1')
21
+
22
+ self._value = value
23
+
24
+ def __repr__(self):
25
+ return f'Bit({self.to_text()})'
26
+
27
+ def to_list(self):
28
+ return self._value.tolist()
29
+
30
+ def to_numpy(self):
31
+ return self._value
32
+
33
+ def to_text(self):
34
+ return ''.join(self._value.astype(np.uint8).astype(str))
35
+
36
+ def to_binary(self):
37
+ return pack('>i', len(self._value)) + np.packbits(self._value).tobytes()
38
+
39
+ @classmethod
40
+ def from_text(cls, value):
41
+ return cls(np.asarray([v != '0' for v in value], dtype=bool))
42
+
43
+ @classmethod
44
+ def from_binary(cls, value):
45
+ count = unpack_from('>i', value)[0]
46
+ buf = np.frombuffer(value, dtype=np.uint8, offset=4)
47
+ return cls(np.unpackbits(buf, count=count).astype(bool))
48
+
49
+ @classmethod
50
+ def _to_db(cls, value):
51
+ if not isinstance(value, cls):
52
+ raise ValueError('expected bit')
53
+
54
+ return value.to_text()
55
+
56
+ @classmethod
57
+ def _to_db_binary(cls, value):
58
+ if not isinstance(value, cls):
59
+ raise ValueError('expected bit')
60
+
61
+ return value.to_binary()
@@ -0,0 +1,78 @@
1
+ import numpy as np
2
+ from struct import pack, unpack_from
3
+
4
+
5
+ class HalfVector:
6
+ def __init__(self, value):
7
+ # asarray still copies if same dtype
8
+ if not isinstance(value, np.ndarray) or value.dtype != '>f2':
9
+ value = np.asarray(value, dtype='>f2')
10
+
11
+ if value.ndim != 1:
12
+ raise ValueError('expected ndim to be 1')
13
+
14
+ self._value = value
15
+
16
+ def __repr__(self):
17
+ return f'HalfVector({self.to_list()})'
18
+
19
+ def dimensions(self):
20
+ return len(self._value)
21
+
22
+ def to_list(self):
23
+ return self._value.tolist()
24
+
25
+ def to_numpy(self):
26
+ return self._value
27
+
28
+ def to_text(self):
29
+ return '[' + ','.join([str(float(v)) for v in self._value]) + ']'
30
+
31
+ def to_binary(self):
32
+ return pack('>HH', self.dimensions(), 0) + self._value.tobytes()
33
+
34
+ @classmethod
35
+ def from_text(cls, value):
36
+ return cls([float(v) for v in value[1:-1].split(',')])
37
+
38
+ @classmethod
39
+ def from_binary(cls, value):
40
+ dim, unused = unpack_from('>HH', value)
41
+ return cls(np.frombuffer(value, dtype='>f2', count=dim, offset=4))
42
+
43
+ @classmethod
44
+ def _to_db(cls, value, dim=None):
45
+ if value is None:
46
+ return value
47
+
48
+ if not isinstance(value, cls):
49
+ value = cls(value)
50
+
51
+ if dim is not None and value.dimensions() != dim:
52
+ raise ValueError('expected %d dimensions, not %d' % (dim, value.dimensions()))
53
+
54
+ return value.to_text()
55
+
56
+ @classmethod
57
+ def _to_db_binary(cls, value):
58
+ if value is None:
59
+ return value
60
+
61
+ if not isinstance(value, cls):
62
+ value = cls(value)
63
+
64
+ return value.to_binary()
65
+
66
+ @classmethod
67
+ def _from_db(cls, value):
68
+ if value is None or isinstance(value, cls):
69
+ return value
70
+
71
+ return cls.from_text(value)
72
+
73
+ @classmethod
74
+ def _from_db_binary(cls, value):
75
+ if value is None or isinstance(value, cls):
76
+ return value
77
+
78
+ return cls.from_binary(value)