pyobvector 0.2.19__py3-none-any.whl → 0.2.21__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.
@@ -1,6 +1,6 @@
1
1
  """FieldSchema & CollectionSchema definition module to be compatible with Milvus."""
2
2
  import copy
3
- from typing import Optional, List
3
+ from typing import Optional
4
4
  from sqlalchemy import Column
5
5
  from .schema_type import DataType, convert_datatype_to_sqltype
6
6
  from .exceptions import *
@@ -125,7 +125,7 @@ class CollectionSchema:
125
125
  """
126
126
  def __init__(
127
127
  self,
128
- fields: Optional[List[FieldSchema]] = None,
128
+ fields: Optional[list[FieldSchema]] = None,
129
129
  partitions: Optional[ObPartition] = None,
130
130
  description: str = "", # ignored in oceanbase
131
131
  **kwargs,
@@ -85,13 +85,13 @@ class ExceptionsMessage:
85
85
  "Range expression is necessary when partition type is Range"
86
86
  )
87
87
  PartitionRangeColNameListMissing = (
88
- "Column name list is necessary when parititon type is RangeColumns"
88
+ "Column name list is necessary when partition type is RangeColumns"
89
89
  )
90
90
  PartitionListExprMissing = (
91
91
  "List expression is necessary when partition type is List"
92
92
  )
93
93
  PartitionListColNameListMissing = (
94
- "Column name list is necessary when parititon type is ListColumns"
94
+ "Column name list is necessary when partition type is ListColumns"
95
95
  )
96
96
  PartitionHashNameListAndPartCntMissing = (
97
97
  "One of hash_part_name_list and part_count must be set when partition type is Hash"
@@ -1,28 +1,58 @@
1
1
  """A module to specify fts index parameters"""
2
2
  from enum import Enum
3
- from typing import List, Optional
3
+ from typing import Optional, Union
4
4
 
5
5
  class FtsParser(Enum):
6
+ """Built-in full-text search parser types supported by OceanBase"""
6
7
  IK = 0
7
8
  NGRAM = 1
9
+ NGRAM2 = 2 # NGRAM2 parser (supported from V4.3.5 BP2+)
10
+ BASIC_ENGLISH = 3 # Basic English parser
11
+ JIEBA = 4 # jieba parser
8
12
 
9
13
 
10
14
  class FtsIndexParam:
15
+ """Full-text search index parameter.
16
+
17
+ Args:
18
+ index_name: Index name
19
+ field_names: List of field names to create full-text index on
20
+ parser_type: Parser type, can be FtsParser enum or string (for custom parsers)
21
+ If None, uses default Space parser
22
+ """
11
23
  def __init__(
12
24
  self,
13
25
  index_name: str,
14
- field_names: List[str],
15
- parser_type: Optional[FtsParser],
26
+ field_names: list[str],
27
+ parser_type: Optional[Union[FtsParser, str]] = None,
16
28
  ):
17
29
  self.index_name = index_name
18
30
  self.field_names = field_names
19
31
  self.parser_type = parser_type
20
32
 
21
- def param_str(self) -> str | None:
22
- if self.parser_type == FtsParser.IK:
23
- return "ik"
24
- if self.parser_type == FtsParser.NGRAM:
25
- return "ngram"
33
+ def param_str(self) -> Optional[str]:
34
+ """Convert parser type to string format for SQL."""
35
+ if self.parser_type is None:
36
+ return None # Default Space parser, no need to specify
37
+
38
+ if isinstance(self.parser_type, str):
39
+ # Custom parser name (e.g., "thai_ftparser")
40
+ return self.parser_type.lower()
41
+
42
+ if isinstance(self.parser_type, FtsParser):
43
+ if self.parser_type == FtsParser.IK:
44
+ return "ik"
45
+ if self.parser_type == FtsParser.NGRAM:
46
+ return "ngram"
47
+ if self.parser_type == FtsParser.NGRAM2:
48
+ return "ngram2"
49
+ if self.parser_type == FtsParser.BASIC_ENGLISH:
50
+ return "beng"
51
+ if self.parser_type == FtsParser.JIEBA:
52
+ return "jieba"
53
+ # Raise exception for unrecognized FtsParser enum values
54
+ raise ValueError(f"Unrecognized FtsParser enum value: {self.parser_type}")
55
+
26
56
  return None
27
57
 
28
58
  def __iter__(self):
@@ -34,7 +64,7 @@ class FtsIndexParam:
34
64
  def __str__(self):
35
65
  return str(dict(self))
36
66
 
37
- def __eq__(self, other: None):
67
+ def __eq__(self, other: object) -> bool:
38
68
  if isinstance(other, self.__class__):
39
69
  return dict(self) == dict(other)
40
70
 
@@ -1,7 +1,7 @@
1
1
  """OceanBase Hybrid Search Client."""
2
2
  import json
3
3
  import logging
4
- from typing import Dict, Any
4
+ from typing import Any
5
5
 
6
6
  from sqlalchemy import text
7
7
 
@@ -30,13 +30,9 @@ class HybridSearch(Client):
30
30
 
31
31
  if self.ob_version < min_required_version:
32
32
  # For versions < 4.4.1.0, check if it's SeekDB
33
- with self.engine.connect() as conn:
34
- with conn.begin():
35
- res = conn.execute(text("SELECT version()"))
36
- version_str = [r[0] for r in res][0]
37
- if "SeekDB" in version_str:
38
- logger.info(f"SeekDB detected in version string: {version_str}, allowing hybrid search")
39
- return
33
+ if self._is_seekdb():
34
+ logger.info("SeekDB detected, allowing hybrid search")
35
+ return
40
36
  raise ClusterVersionException(
41
37
  code=ErrorCode.NOT_SUPPORTED,
42
38
  message=ExceptionsMessage.ClusterVersionIsLow % ("Hybrid Search", "4.4.1.0"),
@@ -45,7 +41,7 @@ class HybridSearch(Client):
45
41
  def search(
46
42
  self,
47
43
  index: str,
48
- body: Dict[str, Any],
44
+ body: dict[str, Any],
49
45
  **kwargs,
50
46
  ):
51
47
  """Execute hybrid search with parameter compatible with Elasticsearch.
@@ -70,7 +66,7 @@ class HybridSearch(Client):
70
66
  def get_sql(
71
67
  self,
72
68
  index: str,
73
- body: Dict[str, Any],
69
+ body: dict[str, Any],
74
70
  ) -> str:
75
71
  """Get the SQL actually to be executed in hybrid search.
76
72
 
@@ -134,8 +134,11 @@ class IndexParam:
134
134
  if 'efSearch' in params:
135
135
  ob_params['ef_search'] = params['efSearch']
136
136
 
137
- if self.is_index_type_sparse_vector() and ob_params['distance'] != 'inner_product':
138
- raise ValueError("Metric type should be 'inner_product' for sparse vector index.")
137
+ if self.is_index_type_sparse_vector():
138
+ if ob_params['distance'] != 'inner_product':
139
+ raise ValueError("Metric type should be 'inner_product' for sparse vector index.")
140
+ if 'sparse_index_type' in self.kwargs:
141
+ ob_params['type'] = self.kwargs['sparse_index_type']
139
142
  return ob_params
140
143
 
141
144
  def param_str(self):
@@ -1,7 +1,7 @@
1
1
  """Milvus Like Client."""
2
2
  import logging
3
3
  import json
4
- from typing import Optional, Union, Dict, List
4
+ from typing import Optional, Union
5
5
 
6
6
  from sqlalchemy.exc import NoSuchTableError
7
7
  from sqlalchemy import (
@@ -147,7 +147,7 @@ class MilvusLikeClient(Client):
147
147
 
148
148
  def get_collection_stats(
149
149
  self, collection_name: str, timeout: Optional[float] = None # pylint: disable=unused-argument
150
- ) -> Dict:
150
+ ) -> dict:
151
151
  """Get collection row count.
152
152
 
153
153
  Args:
@@ -354,12 +354,12 @@ class MilvusLikeClient(Client):
354
354
  with_dist: bool = False,
355
355
  flter=None,
356
356
  limit: int = 10,
357
- output_fields: Optional[List[str]] = None,
357
+ output_fields: Optional[list[str]] = None,
358
358
  search_params: Optional[dict] = None,
359
359
  timeout: Optional[float] = None, # pylint: disable=unused-argument
360
- partition_names: Optional[List[str]] = None,
360
+ partition_names: Optional[list[str]] = None,
361
361
  **kwargs, # pylint: disable=unused-argument
362
- ) -> List[dict]:
362
+ ) -> list[dict]:
363
363
  """Perform ann search.
364
364
  Note: OceanBase does not support batch search now. `data` & the return value is not a batch.
365
365
 
@@ -467,11 +467,11 @@ class MilvusLikeClient(Client):
467
467
  self,
468
468
  collection_name: str,
469
469
  flter=None,
470
- output_fields: Optional[List[str]] = None,
470
+ output_fields: Optional[list[str]] = None,
471
471
  timeout: Optional[float] = None, # pylint: disable=unused-argument
472
- partition_names: Optional[List[str]] = None,
472
+ partition_names: Optional[list[str]] = None,
473
473
  **kwargs, # pylint: disable=unused-argument
474
- ) -> List[dict]:
474
+ ) -> list[dict]:
475
475
  """Query records.
476
476
 
477
477
  Args:
@@ -533,11 +533,11 @@ class MilvusLikeClient(Client):
533
533
  self,
534
534
  collection_name: str,
535
535
  ids: Union[list, str, int] = None,
536
- output_fields: Optional[List[str]] = None,
536
+ output_fields: Optional[list[str]] = None,
537
537
  timeout: Optional[float] = None, # pylint: disable=unused-argument
538
- partition_names: Optional[List[str]] = None,
538
+ partition_names: Optional[list[str]] = None,
539
539
  **kwargs, # pylint: disable=unused-argument
540
- ) -> List[dict]:
540
+ ) -> list[dict]:
541
541
  """Get records with specified primary field `ids`.
542
542
 
543
543
  Args:
@@ -672,7 +672,7 @@ class MilvusLikeClient(Client):
672
672
  def insert(
673
673
  self,
674
674
  collection_name: str,
675
- data: Union[Dict, List[Dict]],
675
+ data: Union[dict, list[dict]],
676
676
  timeout: Optional[float] = None,
677
677
  partition_name: Optional[str] = "",
678
678
  ) -> (
@@ -700,10 +700,10 @@ class MilvusLikeClient(Client):
700
700
  def upsert(
701
701
  self,
702
702
  collection_name: str,
703
- data: Union[Dict, List[Dict]],
703
+ data: Union[dict, list[dict]],
704
704
  timeout: Optional[float] = None, # pylint: disable=unused-argument
705
705
  partition_name: Optional[str] = "",
706
- ) -> List[Union[str, int]]:
706
+ ) -> list[Union[str, int]]:
707
707
  """Update data in table. If primary key is duplicated, replace it.
708
708
 
709
709
  Args:
@@ -1,5 +1,5 @@
1
1
  import logging
2
- from typing import List, Optional, Dict, Union
2
+ from typing import Optional, Union
3
3
  from urllib.parse import quote
4
4
 
5
5
  import sqlalchemy.sql.functions as func_mod
@@ -93,6 +93,26 @@ class ObClient:
93
93
  self.metadata_obj.clear()
94
94
  self.metadata_obj.reflect(bind=self.engine, extend_existing=True)
95
95
 
96
+ def _is_seekdb(self) -> bool:
97
+ """Check if the database is SeekDB by querying version.
98
+
99
+ Returns:
100
+ bool: True if database is SeekDB, False otherwise
101
+ """
102
+ is_seekdb = False
103
+ try:
104
+ if hasattr(self, '_is_seekdb_cached'):
105
+ return self._is_seekdb_cached
106
+ with self.engine.connect() as conn:
107
+ result = conn.execute(text("SELECT VERSION()"))
108
+ version_str = [r[0] for r in result][0]
109
+ is_seekdb = "SeekDB" in version_str
110
+ self._is_seekdb_cached = is_seekdb
111
+ logger.debug(f"Version query result: {version_str}, is_seekdb: {is_seekdb}")
112
+ except Exception as e:
113
+ logger.warning(f"Failed to query version: {e}")
114
+ return is_seekdb
115
+
96
116
  def _insert_partition_hint_for_query_sql(self, sql: str, partition_hint: str):
97
117
  from_index = sql.find("FROM")
98
118
  assert from_index != -1
@@ -121,8 +141,8 @@ class ObClient:
121
141
  def create_table(
122
142
  self,
123
143
  table_name: str,
124
- columns: List[Column],
125
- indexes: Optional[List[Index]] = None,
144
+ columns: list[Column],
145
+ indexes: Optional[list[Index]] = None,
126
146
  partitions: Optional[ObPartition] = None,
127
147
  **kwargs,
128
148
  ):
@@ -188,7 +208,7 @@ class ObClient:
188
208
  def insert(
189
209
  self,
190
210
  table_name: str,
191
- data: Union[Dict, List[Dict]],
211
+ data: Union[dict, list[dict]],
192
212
  partition_name: Optional[str] = "",
193
213
  ):
194
214
  """Insert data into table.
@@ -198,7 +218,7 @@ class ObClient:
198
218
  data (Union[Dict, List[Dict]]): data that will be inserted
199
219
  partition_name (Optional[str]): limit the query to certain partition
200
220
  """
201
- if isinstance(data, Dict):
221
+ if isinstance(data, dict):
202
222
  data = [data]
203
223
 
204
224
  if len(data) == 0:
@@ -220,7 +240,7 @@ class ObClient:
220
240
  def upsert(
221
241
  self,
222
242
  table_name: str,
223
- data: Union[Dict, List[Dict]],
243
+ data: Union[dict, list[dict]],
224
244
  partition_name: Optional[str] = "",
225
245
  ):
226
246
  """Update data in table. If primary key is duplicated, replace it.
@@ -230,7 +250,7 @@ class ObClient:
230
250
  data (Union[Dict, List[Dict]]): data that will be upserted
231
251
  partition_name (Optional[str]): limit the query to certain partition
232
252
  """
233
- if isinstance(data, Dict):
253
+ if isinstance(data, dict):
234
254
  data = [data]
235
255
 
236
256
  if len(data) == 0:
@@ -345,8 +365,8 @@ class ObClient:
345
365
  table_name: str,
346
366
  ids: Optional[Union[list, str, int]] = None,
347
367
  where_clause=None,
348
- output_column_name: Optional[List[str]] = None,
349
- partition_names: Optional[List[str]] = None,
368
+ output_column_name: Optional[list[str]] = None,
369
+ partition_names: Optional[list[str]] = None,
350
370
  n_limits: Optional[int] = None,
351
371
  ):
352
372
  """Get records with specified primary field `ids`.
@@ -1,6 +1,6 @@
1
1
  """OceanBase Vector Store Client."""
2
2
  import logging
3
- from typing import List, Optional, Union
3
+ from typing import Optional, Union
4
4
 
5
5
  import numpy as np
6
6
  from sqlalchemy import (
@@ -60,10 +60,10 @@ class ObVecClient(ObClient):
60
60
  def create_table_with_index_params(
61
61
  self,
62
62
  table_name: str,
63
- columns: List[Column],
64
- indexes: Optional[List[Index]] = None,
63
+ columns: list[Column],
64
+ indexes: Optional[list[Index]] = None,
65
65
  vidxs: Optional[IndexParams] = None,
66
- fts_idxs: Optional[List[FtsIndexParam]] = None,
66
+ fts_idxs: Optional[list[FtsIndexParam]] = None,
67
67
  partitions: Optional[ObPartition] = None,
68
68
  ):
69
69
  """Create table with optional index_params.
@@ -99,7 +99,11 @@ class ObVecClient(ObClient):
99
99
  create_table_sql = str(CreateTable(table).compile(self.engine))
100
100
  new_sql = create_table_sql[:create_table_sql.rfind(')')]
101
101
  for sparse_vidx in sparse_vidxs:
102
- new_sql += f",\n\tVECTOR INDEX {sparse_vidx.index_name}({sparse_vidx.field_name}) with (distance=inner_product)"
102
+ sparse_params = sparse_vidx._parse_kwargs()
103
+ if 'type' in sparse_params:
104
+ new_sql += f",\n\tVECTOR INDEX {sparse_vidx.index_name}({sparse_vidx.field_name}) with (type={sparse_params['type']}, distance=inner_product)"
105
+ else:
106
+ new_sql += f",\n\tVECTOR INDEX {sparse_vidx.index_name}({sparse_vidx.field_name}) with (distance=inner_product)"
103
107
  new_sql += "\n)"
104
108
  conn.execute(text(new_sql))
105
109
  else:
@@ -136,7 +140,7 @@ class ObVecClient(ObClient):
136
140
  table_name: str,
137
141
  is_vec_index: bool,
138
142
  index_name: str,
139
- column_names: List[str],
143
+ column_names: list[str],
140
144
  vidx_params: Optional[str] = None,
141
145
  **kw,
142
146
  ):
@@ -270,12 +274,12 @@ class ObVecClient(ObClient):
270
274
  distance_func,
271
275
  with_dist: bool = False,
272
276
  topk: int = 10,
273
- output_column_names: Optional[List[str]] = None,
274
- output_columns: Optional[Union[List, tuple]] = None,
275
- extra_output_cols: Optional[List] = None,
277
+ output_column_names: Optional[list[str]] = None,
278
+ output_columns: Optional[Union[list, tuple]] = None,
279
+ extra_output_cols: Optional[list] = None,
276
280
  where_clause=None,
277
- partition_names: Optional[List[str]] = None,
278
- idx_name_hint: Optional[List[str]] = None,
281
+ partition_names: Optional[list[str]] = None,
282
+ idx_name_hint: Optional[list[str]] = None,
279
283
  distance_threshold: Optional[float] = None,
280
284
  **kwargs,
281
285
  ): # pylint: disable=unused-argument
@@ -399,11 +403,11 @@ class ObVecClient(ObClient):
399
403
  distance_func,
400
404
  with_dist: bool = False,
401
405
  topk: int = 10,
402
- output_column_names: Optional[List[str]] = None,
403
- extra_output_cols: Optional[List] = None,
406
+ output_column_names: Optional[list[str]] = None,
407
+ extra_output_cols: Optional[list] = None,
404
408
  where_clause=None,
405
- partition_names: Optional[List[str]] = None,
406
- str_list: Optional[List[str]] = None,
409
+ partition_names: Optional[list[str]] = None,
410
+ str_list: Optional[list[str]] = None,
407
411
  **kwargs,
408
412
  ): # pylint: disable=unused-argument
409
413
  """Perform post ann search.
@@ -479,7 +483,7 @@ class ObVecClient(ObClient):
479
483
  vec_column_name: str,
480
484
  distance_func,
481
485
  topk: int = 10,
482
- output_column_names: Optional[List[str]] = None,
486
+ output_column_names: Optional[list[str]] = None,
483
487
  where_clause=None,
484
488
  **kwargs,
485
489
  ): # pylint: disable=unused-argument
@@ -1,18 +1,16 @@
1
1
  import json
2
2
  import logging
3
3
  import re
4
- from typing import Dict, List, Optional, Any, Union
4
+ from typing import Optional, Union
5
5
 
6
6
  from sqlalchemy import Column, Integer, String, JSON, Engine, select, text, func, CursorResult
7
7
  from sqlalchemy.dialects.mysql import TINYINT
8
8
  from sqlalchemy.orm import declarative_base, sessionmaker, Session
9
9
  from sqlglot import parse_one, exp, Expression, to_identifier
10
- from sqlglot.expressions import Concat
11
10
 
12
11
 
13
12
  from .ob_vec_client import ObVecClient
14
13
  from ..json_table import (
15
- OceanBase,
16
14
  ChangeColumn,
17
15
  JsonTableBool,
18
16
  JsonTableTimestamp,
@@ -58,7 +56,7 @@ class ObVecJsonTableClient(ObVecClient):
58
56
  class JsonTableMetadata:
59
57
  def __init__(self, user_id: str):
60
58
  self.user_id = user_id
61
- self.meta_cache: Dict[str, List] = {}
59
+ self.meta_cache: dict[str, list] = {}
62
60
 
63
61
  @classmethod
64
62
  def _parse_col_type(cls, col_type: str):
@@ -319,7 +317,7 @@ class ObVecJsonTableClient(ObVecClient):
319
317
  def _check_table_exists(self, jtable_name: str) -> bool:
320
318
  return jtable_name in self.jmetadata.meta_cache
321
319
 
322
- def _check_col_exists(self, jtable_name: str, col_name: str) -> Optional[Dict]:
320
+ def _check_col_exists(self, jtable_name: str, col_name: str) -> Optional[dict]:
323
321
  if not self._check_table_exists(jtable_name):
324
322
  return None
325
323
  for col_meta in self.jmetadata.meta_cache[jtable_name]:
@@ -339,7 +337,7 @@ class ObVecJsonTableClient(ObVecClient):
339
337
  col_type_str += '(' + ','.join(col_type_params_list) + ')'
340
338
  return col_type_str
341
339
 
342
- def _parse_col_constraints(self, expr: Expression) -> Dict:
340
+ def _parse_col_constraints(self, expr: Expression) -> dict:
343
341
  col_has_default = False
344
342
  col_nullable = True
345
343
  for cons in expr:
@@ -817,11 +815,12 @@ class ObVecJsonTableClient(ObVecClient):
817
815
  ):
818
816
  real_user_id = opt_user_id or self.user_id
819
817
 
820
- table_name = ast.args['from'].this.this.this
818
+ from_key = 'from_' if 'from_' in ast.args else 'from'
819
+ table_name = ast.args[from_key].this.this.this
821
820
  if not self._check_table_exists(table_name):
822
821
  raise ValueError(f"Table {table_name} does not exists")
823
822
 
824
- ast.args['from'].args['this'].args['this'] = to_identifier(name=JSON_TABLE_DATA_TABLE_NAME, quoted=False)
823
+ ast.args[from_key].args['this'].args['this'] = to_identifier(name=JSON_TABLE_DATA_TABLE_NAME, quoted=False)
825
824
 
826
825
  col_meta = self.jmetadata.meta_cache[table_name]
827
826
  json_table_meta_str = []
@@ -883,12 +882,19 @@ class ObVecJsonTableClient(ObVecClient):
883
882
  identifier.args['quoted'] = False
884
883
  col.args['table'] = identifier
885
884
 
886
- join_clause = parse_one(f"from t1, {json_table_str}")
887
- join_node = join_clause.args['joins'][0]
888
- if 'joins' in ast.args.keys():
889
- ast.args['joins'].append(join_node)
890
- else:
891
- ast.args['joins'] = [join_node]
885
+ # Manually create the JOIN node for json_table
886
+ # In some versions of sqlglot, comma-separated tables may not be parsed as
887
+ # explicit JOINS, so we directly parse the json_table expression and create a JOIN node
888
+ # explicitly
889
+ json_table_expr = parse_one(json_table_str, dialect="oceanbase")
890
+
891
+ join_node = exp.Join()
892
+ join_node.args['this'] = json_table_expr
893
+ join_node.args['kind'] = None # CROSS JOIN (implicit join with comma)
894
+
895
+ if 'joins' not in ast.args:
896
+ ast.args['joins'] = []
897
+ ast.args['joins'].append(join_node)
892
898
 
893
899
  if real_user_id:
894
900
  extra_filter_str = f"{JSON_TABLE_DATA_TABLE_NAME}.user_id = '{real_user_id}' AND {JSON_TABLE_DATA_TABLE_NAME}.jtable_name = '{table_name}'"
@@ -1,5 +1,5 @@
1
1
  """A module to do compilation of OceanBase Parition Clause."""
2
- from typing import List, Optional, Union
2
+ from typing import Optional, Union
3
3
  import logging
4
4
  from dataclasses import dataclass
5
5
  from .enum import IntEnum
@@ -69,11 +69,11 @@ class RangeListPartInfo:
69
69
  Using 100 / `MAXVALUE` when create Range/RangeColumns partition.
70
70
  """
71
71
  part_name: str
72
- part_upper_bound_expr: Union[List, str, int]
72
+ part_upper_bound_expr: Union[list, str, int]
73
73
 
74
74
  def get_part_expr_str(self):
75
75
  """Parse part_upper_bound_expr to text SQL."""
76
- if isinstance(self.part_upper_bound_expr, List):
76
+ if isinstance(self.part_upper_bound_expr, list):
77
77
  return ",".join([str(v) for v in self.part_upper_bound_expr])
78
78
  if isinstance(self.part_upper_bound_expr, str):
79
79
  return self.part_upper_bound_expr
@@ -87,9 +87,9 @@ class ObRangePartition(ObPartition):
87
87
  def __init__(
88
88
  self,
89
89
  is_range_columns: bool,
90
- range_part_infos: List[RangeListPartInfo],
90
+ range_part_infos: list[RangeListPartInfo],
91
91
  range_expr: Optional[str] = None,
92
- col_name_list: Optional[List[str]] = None,
92
+ col_name_list: Optional[list[str]] = None,
93
93
  ):
94
94
  super().__init__(PartType.RangeColumns if is_range_columns else PartType.Range)
95
95
  self.range_part_infos = range_part_infos
@@ -140,9 +140,9 @@ class ObSubRangePartition(ObRangePartition):
140
140
  def __init__(
141
141
  self,
142
142
  is_range_columns: bool,
143
- range_part_infos: List[RangeListPartInfo],
143
+ range_part_infos: list[RangeListPartInfo],
144
144
  range_expr: Optional[str] = None,
145
- col_name_list: Optional[List[str]] = None,
145
+ col_name_list: Optional[list[str]] = None,
146
146
  ):
147
147
  super().__init__(is_range_columns, range_part_infos, range_expr, col_name_list)
148
148
  self.is_sub = True
@@ -176,9 +176,9 @@ class ObListPartition(ObPartition):
176
176
  def __init__(
177
177
  self,
178
178
  is_list_columns: bool,
179
- list_part_infos: List[RangeListPartInfo],
179
+ list_part_infos: list[RangeListPartInfo],
180
180
  list_expr: Optional[str] = None,
181
- col_name_list: Optional[List[str]] = None,
181
+ col_name_list: Optional[list[str]] = None,
182
182
  ):
183
183
  super().__init__(PartType.ListColumns if is_list_columns else PartType.List)
184
184
  self.list_part_infos = list_part_infos
@@ -228,9 +228,9 @@ class ObSubListPartition(ObListPartition):
228
228
  def __init__(
229
229
  self,
230
230
  is_list_columns: bool,
231
- list_part_infos: List[RangeListPartInfo],
231
+ list_part_infos: list[RangeListPartInfo],
232
232
  list_expr: Optional[str] = None,
233
- col_name_list: Optional[List[str]] = None,
233
+ col_name_list: Optional[list[str]] = None,
234
234
  ):
235
235
  super().__init__(is_list_columns, list_part_infos, list_expr, col_name_list)
236
236
  self.is_sub = True
@@ -263,7 +263,7 @@ class ObHashPartition(ObPartition):
263
263
  def __init__(
264
264
  self,
265
265
  hash_expr: str,
266
- hash_part_name_list: List[str] = None,
266
+ hash_part_name_list: list[str] = None,
267
267
  part_count: Optional[int] = None,
268
268
  ):
269
269
  super().__init__(PartType.Hash)
@@ -308,7 +308,7 @@ class ObSubHashPartition(ObHashPartition):
308
308
  def __init__(
309
309
  self,
310
310
  hash_expr: str,
311
- hash_part_name_list: List[str] = None,
311
+ hash_part_name_list: list[str] = None,
312
312
  part_count: Optional[int] = None,
313
313
  ):
314
314
  super().__init__(hash_expr, hash_part_name_list, part_count)
@@ -334,8 +334,8 @@ class ObKeyPartition(ObPartition):
334
334
  """Key partition strategy."""
335
335
  def __init__(
336
336
  self,
337
- col_name_list: List[str],
338
- key_part_name_list: List[str] = None,
337
+ col_name_list: list[str],
338
+ key_part_name_list: list[str] = None,
339
339
  part_count: Optional[int] = None,
340
340
  ):
341
341
  super().__init__(PartType.Key)
@@ -381,8 +381,8 @@ class ObSubKeyPartition(ObKeyPartition):
381
381
  """Key subpartition strategy."""
382
382
  def __init__(
383
383
  self,
384
- col_name_list: List[str],
385
- key_part_name_list: List[str] = None,
384
+ col_name_list: list[str],
385
+ key_part_name_list: list[str] = None,
386
386
  part_count: Optional[int] = None,
387
387
  ):
388
388
  super().__init__(col_name_list, key_part_name_list, part_count)
@@ -1,10 +1,9 @@
1
1
  import logging
2
2
  import re
3
- from typing import Tuple
4
3
 
5
4
  from sqlalchemy.ext.compiler import compiles
6
5
  from sqlalchemy.sql.functions import FunctionElement
7
- from sqlalchemy import BINARY, Float, Boolean, Text
6
+ from sqlalchemy import Text
8
7
 
9
8
  logger = logging.getLogger(__name__)
10
9
 
@@ -2,7 +2,7 @@ from datetime import datetime
2
2
  from decimal import Decimal, InvalidOperation, ROUND_DOWN
3
3
  from enum import Enum
4
4
  from typing import Optional
5
- from typing_extensions import Annotated
5
+ from typing import Annotated
6
6
 
7
7
  from pydantic import BaseModel, Field, AfterValidator, create_model
8
8
 
@@ -1,6 +1,7 @@
1
1
  """ARRAY: An extended data type for SQLAlchemy"""
2
2
  import json
3
- from typing import Any, List, Optional, Sequence, Union
3
+ from typing import Any, Optional, Union
4
+ from collections.abc import Sequence
4
5
 
5
6
  from sqlalchemy.sql.type_api import TypeEngine
6
7
  from sqlalchemy.types import UserDefinedType, String
@@ -49,7 +50,7 @@ class ARRAY(UserDefinedType):
49
50
 
50
51
  def _validate_dimension(self, value: list[Any]):
51
52
  arr_depth = self._get_list_depth(value)
52
- assert arr_depth == self.dim, "Array dimension mismatch, expected {}, got {}".format(self.dim, arr_depth)
53
+ assert arr_depth == self.dim, f"Array dimension mismatch, expected {self.dim}, got {arr_depth}"
53
54
 
54
55
  def bind_processor(self, dialect):
55
56
  item_type = self.item_type
@@ -58,7 +59,7 @@ class ARRAY(UserDefinedType):
58
59
 
59
60
  item_proc = item_type.dialect_impl(dialect).bind_processor(dialect)
60
61
 
61
- def process(value: Optional[Sequence[Any] | str]) -> Optional[str]:
62
+ def process(value: Optional[Union[Sequence[Any] | str]]) -> Optional[str]:
62
63
  if value is None:
63
64
  return None
64
65
  if isinstance(value, str):
@@ -85,7 +86,7 @@ class ARRAY(UserDefinedType):
85
86
 
86
87
  item_proc = item_type.dialect_impl(dialect).result_processor(dialect, coltype)
87
88
 
88
- def process(value: Optional[str]) -> Optional[List[Any]]:
89
+ def process(value: Optional[str]) -> Optional[list[Any]]:
89
90
  if value is None:
90
91
  return None
91
92
 
@@ -1,5 +1,5 @@
1
1
  """Point: OceanBase GIS data type for SQLAlchemy"""
2
- from typing import Tuple, Optional
2
+ from typing import Optional
3
3
  from sqlalchemy.types import UserDefinedType, String
4
4
 
5
5
  class POINT(UserDefinedType):
@@ -24,7 +24,7 @@ class POINT(UserDefinedType):
24
24
  return f"POINT SRID {self.srid}"
25
25
 
26
26
  @classmethod
27
- def to_db(cls, value: Tuple[float, float]):
27
+ def to_db(cls, value: tuple[float, float]):
28
28
  """Parse tuple to POINT literal"""
29
29
  return f"POINT({value[0]} {value[1]})"
30
30
 
@@ -1,7 +1,6 @@
1
1
  """gis_func: An extended system function in GIS."""
2
2
 
3
3
  import logging
4
- from typing import Tuple
5
4
 
6
5
  from sqlalchemy.ext.compiler import compiles
7
6
  from sqlalchemy.sql.functions import FunctionElement
@@ -30,7 +29,7 @@ def compile_ST_GeomFromText(element, compiler, **kwargs): # pylint: disable=unus
30
29
  for idx, arg in enumerate(element.args):
31
30
  if idx == 0:
32
31
  if (
33
- (not isinstance(arg, Tuple)) or
32
+ (not isinstance(arg, tuple)) or
34
33
  (len(arg) != 2) or
35
34
  (not all(isinstance(x, float) for x in arg))
36
35
  ):
@@ -35,12 +35,12 @@ class OceanBaseTableDefinitionParser(MySQLTableDefinitionParser):
35
35
 
36
36
  self._re_array_column = _re_compile(
37
37
  r"\s*"
38
- r"%(iq)s(?P<name>(?:%(esc_fq)s|[^%(fq)s])+)%(fq)s\s+"
38
+ r"{iq}(?P<name>(?:{esc_fq}|[^{fq}])+){fq}\s+"
39
39
  r"(?P<coltype_with_args>(?i:(?<!\w)array(?!\w))\s*\([^()]*(?:\([^()]*\)[^()]*)*\))"
40
40
  r"(?:\s+(?P<notnull>(?:NOT\s+)?NULL))?"
41
41
  r"(?:\s+DEFAULT\s+(?P<default>(?:NULL|'(?:''|[^'])*'|\(.+?\)|[\-\w\.\(\)]+)))?"
42
42
  r"(?:\s+COMMENT\s+'(?P<comment>(?:''|[^'])*)')?"
43
- r"\s*,?\s*$" % quotes
43
+ r"\s*,?\s*$".format(**quotes)
44
44
  )
45
45
 
46
46
  self._re_key = _re_compile(
@@ -1,6 +1,5 @@
1
1
  """OceanBase cluster version module."""
2
2
  import copy
3
- from typing import List
4
3
 
5
4
 
6
5
  class ObVersion:
@@ -9,7 +8,7 @@ class ObVersion:
9
8
  Attributes:
10
9
  version_nums (List[int]): version number of OceanBase cluster. For example, '4.3.3.0'
11
10
  """
12
- def __init__(self, version_nums: List[int]):
11
+ def __init__(self, version_nums: list[int]):
13
12
  self.version_nums = copy.deepcopy(version_nums)
14
13
 
15
14
  @classmethod
@@ -1,24 +1,20 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pyobvector
3
- Version: 0.2.19
3
+ Version: 0.2.21
4
4
  Summary: A python SDK for OceanBase Vector Store, based on SQLAlchemy, compatible with Milvus API.
5
+ Project-URL: Homepage, https://github.com/oceanbase/pyobvector
6
+ Project-URL: Repository, https://github.com/oceanbase/pyobvector.git
7
+ Author-email: "shanhaikang.shk" <shanhaikang.shk@oceanbase.com>
8
+ License-Expression: Apache-2.0
5
9
  License-File: LICENSE
6
- Author: shanhaikang.shk
7
- Author-email: shanhaikang.shk@oceanbase.com
8
- Requires-Python: >=3.9,<4.0
9
- Classifier: Programming Language :: Python :: 3
10
- Classifier: Programming Language :: Python :: 3.9
11
- Classifier: Programming Language :: Python :: 3.10
12
- Classifier: Programming Language :: Python :: 3.11
13
- Classifier: Programming Language :: Python :: 3.12
14
- Classifier: Programming Language :: Python :: 3.13
15
- Classifier: Programming Language :: Python :: 3.14
16
- Requires-Dist: aiomysql (>=0.3.2,<0.4.0)
17
- Requires-Dist: numpy (>=1.17.0)
18
- Requires-Dist: pydantic (>=2.7.0,<3)
19
- Requires-Dist: pymysql (>=1.1.1,<2.0.0)
20
- Requires-Dist: sqlalchemy (>=1.4,<=3)
21
- Requires-Dist: sqlglot (>=26.0.1)
10
+ Keywords: obvector,oceanbase,vector store
11
+ Requires-Python: >=3.9
12
+ Requires-Dist: aiomysql>=0.3.2
13
+ Requires-Dist: numpy>=1.17.0
14
+ Requires-Dist: pydantic<3,>=2.7.0
15
+ Requires-Dist: pymysql>=1.1.1
16
+ Requires-Dist: sqlalchemy<=3,>=1.4
17
+ Requires-Dist: sqlglot>=26.0.1
22
18
  Description-Content-Type: text/markdown
23
19
 
24
20
  # pyobvector
@@ -32,13 +28,13 @@ A python SDK for OceanBase Multimodal Store (Vector Store / Full Text Search / J
32
28
  - git clone this repo, then install with:
33
29
 
34
30
  ```shell
35
- poetry install
31
+ uv sync
36
32
  ```
37
33
 
38
34
  - install with pip:
39
35
 
40
36
  ```shell
41
- pip install pyobvector==0.2.19
37
+ pip install pyobvector==0.2.21
42
38
  ```
43
39
 
44
40
  ## Build Doc
@@ -234,17 +230,17 @@ res = self.client.ann_search(
234
230
  The `ann_search` method supports flexible output column selection through the `output_columns` parameter:
235
231
 
236
232
  - **`output_columns`** (recommended): Accepts SQLAlchemy Column objects, expressions, or a mix of both
237
-
233
+
238
234
  - Column objects: `table.c.id`, `table.c.name`
239
235
  - Expressions: `(table.c.age + 10).label('age_plus_10')`
240
236
  - JSON queries: `text("JSON_EXTRACT(meta, '$.key') as extracted_key")`
241
237
  - String functions: `func.concat(table.c.name, ' (', table.c.age, ')').label('name_age')`
242
238
  - **`output_column_names`** (legacy): Accepts list of column name strings
243
-
239
+
244
240
  - Example: `['id', 'name', 'meta']`
245
241
  - **Parameter Priority**: `output_columns` takes precedence over `output_column_names` when both are provided
246
242
  - **`distance_threshold`** (optional): Filter results by distance threshold
247
-
243
+
248
244
  - Type: `Optional[float]`
249
245
  - Only returns results where `distance <= threshold`
250
246
  - Example: `distance_threshold=0.5` returns only results with distance <= 0.5
@@ -0,0 +1,40 @@
1
+ pyobvector/__init__.py,sha256=5RtWI_ol3qxj_m7UXTQzByTmGZdIiQ0yyCyHZUhRDas,3888
2
+ pyobvector/client/__init__.py,sha256=fDK2FVdSm3-XCwTqsam7zisic5UMhANUq97r29i27nc,2819
3
+ pyobvector/client/collection_schema.py,sha256=lQTlanxhfZ0W74HUEB7XwvnkwJASxNSsxurXCvMv8nw,5532
4
+ pyobvector/client/enum.py,sha256=3lPjSltITSE694-qOAP4yoX6fzCjKD4WAewmIxFs49o,139
5
+ pyobvector/client/exceptions.py,sha256=UxU05Md2ZGFspZ8pd02B8EAhp8PBbOUJFopa28IwAHI,3777
6
+ pyobvector/client/fts_index_param.py,sha256=owpjerUV4MIxB69dzwMsh7XIosgLB0OXgaLghV7wUYQ,2475
7
+ pyobvector/client/hybrid_search.py,sha256=xctd-UMYPT4FTr2hWNf8dHWRyT0GAAqSDy3tz_oTP00,2565
8
+ pyobvector/client/index_param.py,sha256=Tbu_-qg4SIXIzn1qpTg8Zl1L_RCXCUAEsZPKsA0LsNY,7062
9
+ pyobvector/client/milvus_like_client.py,sha256=waOqsgGtc5FSEM-BECBGCa6m81d_Pxf1r40LFU-LrmE,27875
10
+ pyobvector/client/ob_client.py,sha256=BsWCEGR30Klf2OUe88qLhBm7jRPBkgfjylQoy7B8EkM,16975
11
+ pyobvector/client/ob_vec_client.py,sha256=65189GcKo4sj_494XLNhqed_vKyu4sTIp_Tv9-zgBcY,20235
12
+ pyobvector/client/ob_vec_json_table_client.py,sha256=NFj7-1AQpTMk5XllecAgTKSGAq8qMIa36FM2cuhd_fQ,39605
13
+ pyobvector/client/partitions.py,sha256=CfkSe0ftb0jbdgdvNF9_SvXxcDhpOoS3pe7ezMeNI0c,15615
14
+ pyobvector/client/schema_type.py,sha256=gH2YiBsgkryo-R0GB_NYuRXMvzvrSjOamZTy6pwn2n0,1673
15
+ pyobvector/json_table/__init__.py,sha256=X5MmK3f10oyJleUUFZJFeunMEfzmf6P1f_7094b-FZc,554
16
+ pyobvector/json_table/json_value_returning_func.py,sha256=fW5Rk5vrVfyTPJPciPyb64A0CqSJBwBS-PYbcS9LhOY,1815
17
+ pyobvector/json_table/oceanbase_dialect.py,sha256=lxpbWBQdK18LWXLmGyk_-ODv6VfnwGLHbcpsQMElOUo,4480
18
+ pyobvector/json_table/virtual_data_type.py,sha256=ALffzZgByEwMsC-JdL_iEW3kwJcfqNGaNSIY645oUfo,3456
19
+ pyobvector/schema/__init__.py,sha256=OMn7Cd2E8o_tm2ArbAXS_zRbiDW2sj7YKPLnbpmaueg,2405
20
+ pyobvector/schema/array.py,sha256=YiGIyALN1_XxWuz9YyuiqhpyRN1DIu9n286vq6O09_U,4181
21
+ pyobvector/schema/dialect.py,sha256=6D9A7Niqig2mwK7C8sP7mCP7kYr5be2jV0xqL87mlz4,1999
22
+ pyobvector/schema/full_text_index.py,sha256=ohQX8uTPdRswEJONuN5A-bNv203d0N0b2BsJ7etx71g,2071
23
+ pyobvector/schema/geo_srid_point.py,sha256=BZuiySoemvtLBPYoZ_BRQT4Gd7fcU1C2xdQfkxpb0OQ,1203
24
+ pyobvector/schema/gis_func.py,sha256=sDhD701I45sRCXNY_E20ujjxe0ldAXofd6kTAhMW12g,3093
25
+ pyobvector/schema/match_against_func.py,sha256=ExTQJvAXHaZwBo1Sjy6IlnF1nF6D9xGUsF4f7zaP8Q0,1336
26
+ pyobvector/schema/ob_table.py,sha256=wlb6Oo9LG-sr8XnG_wbX1Qi5CgnS0XUzNL5qTdsncoY,392
27
+ pyobvector/schema/reflection.py,sha256=guS74lW1wzBOCv5FWeXS1a_KnS-5Md9C_dYaNqiK2yA,5903
28
+ pyobvector/schema/replace_stmt.py,sha256=FtGLXHz6DwzD0FOZPn1EZgXdbHZu-K9HIHS02rZqYrE,560
29
+ pyobvector/schema/sparse_vector.py,sha256=ojqUrvKUnxQE8FErB2B58KO540otOJBqiPTMlwcUW2M,1061
30
+ pyobvector/schema/vec_dist_func.py,sha256=4GAWSrhFNDYooBpbBg604wDrByPrewp46Y4VeoDxV7Y,2986
31
+ pyobvector/schema/vector.py,sha256=dFKfPcTOto0jNxVjhvDmJM7Q4wwp6Z-HcZ3K6oZxUMc,1120
32
+ pyobvector/schema/vector_index.py,sha256=D1ZnhJEObWUCd9ESO57KN1ctl10tkEIH_gV0kwGrvu8,2250
33
+ pyobvector/util/__init__.py,sha256=-opvZ4Ya0ByTAhls06547-zW3vkdYRkUH6W5OCKUHD4,388
34
+ pyobvector/util/ob_version.py,sha256=69me12REWn_T6oi7XjVAF_Dfq_iiaEVZjiHdjog57zs,1518
35
+ pyobvector/util/sparse_vector.py,sha256=1IG0CRYfC2z39nGwuG43TImQkWiuPAXOlOnYqJ1hA-o,1275
36
+ pyobvector/util/vector.py,sha256=58glSQqjOSTrGyNhUEIrs9r4F9oaYO_SdPNhMfbSnWs,2392
37
+ pyobvector-0.2.21.dist-info/METADATA,sha256=uuBysjcnC3Xb8Nevi8MfkEgqTheVkjdHFKbTuMheld4,14120
38
+ pyobvector-0.2.21.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
39
+ pyobvector-0.2.21.dist-info/licenses/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
40
+ pyobvector-0.2.21.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: poetry-core 2.2.1
2
+ Generator: hatchling 1.28.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,40 +0,0 @@
1
- pyobvector/__init__.py,sha256=5RtWI_ol3qxj_m7UXTQzByTmGZdIiQ0yyCyHZUhRDas,3888
2
- pyobvector/client/__init__.py,sha256=fDK2FVdSm3-XCwTqsam7zisic5UMhANUq97r29i27nc,2819
3
- pyobvector/client/collection_schema.py,sha256=a7JQk83ZxMsvMDGt5CC_4lz2-skONqKgk-OGUz297hM,5538
4
- pyobvector/client/enum.py,sha256=3lPjSltITSE694-qOAP4yoX6fzCjKD4WAewmIxFs49o,139
5
- pyobvector/client/exceptions.py,sha256=CjoquSCc0H705MtvhpQW_F7_KaviYrKAHkndpF8sJx4,3777
6
- pyobvector/client/fts_index_param.py,sha256=UvU82p9_x444WAQMqhIUPHbqVHV5B3cFazje1Gw-slo,1105
7
- pyobvector/client/hybrid_search.py,sha256=xuD3CJh4jxm9EFjee6sx7zDGogvQT0qTni79F9Url7Q,2840
8
- pyobvector/client/index_param.py,sha256=Dg-FEFQPBRxb7qXChqoLgBSljpdJzF-C7ESUbG9x1mA,6926
9
- pyobvector/client/milvus_like_client.py,sha256=jHxB-ZmIGZiIDxEpurSlAKffiCF_KFNZP-14_vq1RQM,27887
10
- pyobvector/client/ob_client.py,sha256=csSRVWqbhnsaN2gj7FSwL7QExh4vbTx6EyqPznKRSz4,16172
11
- pyobvector/client/ob_vec_client.py,sha256=P6IMtsYxS_S78VsQtltK3UenW91iYfRKa0s1hIc8CI0,19916
12
- pyobvector/client/ob_vec_json_table_client.py,sha256=rq80AfqAKhosLcrBFROAoINVSkr-48xlRH91Jt4pEwA,39246
13
- pyobvector/client/partitions.py,sha256=Bxwr5yVNlXwZc7SXBC03NeqL9giy4Fe6S2qZdHD8xGw,15621
14
- pyobvector/client/schema_type.py,sha256=gH2YiBsgkryo-R0GB_NYuRXMvzvrSjOamZTy6pwn2n0,1673
15
- pyobvector/json_table/__init__.py,sha256=X5MmK3f10oyJleUUFZJFeunMEfzmf6P1f_7094b-FZc,554
16
- pyobvector/json_table/json_value_returning_func.py,sha256=NWSV2zhe2-1KhIprQaFqOH3vUVF46YaHIZUqX66WZKM,1864
17
- pyobvector/json_table/oceanbase_dialect.py,sha256=lxpbWBQdK18LWXLmGyk_-ODv6VfnwGLHbcpsQMElOUo,4480
18
- pyobvector/json_table/virtual_data_type.py,sha256=uQh6ZQ0UbwpVO9TFegGeu4E8bXW7rdLHAXFQJdiEjLs,3467
19
- pyobvector/schema/__init__.py,sha256=OMn7Cd2E8o_tm2ArbAXS_zRbiDW2sj7YKPLnbpmaueg,2405
20
- pyobvector/schema/array.py,sha256=WDWLZbCdu8stK8wlGWfKUjkhWifS8vbsfYUEEJsQOlQ,4163
21
- pyobvector/schema/dialect.py,sha256=6D9A7Niqig2mwK7C8sP7mCP7kYr5be2jV0xqL87mlz4,1999
22
- pyobvector/schema/full_text_index.py,sha256=ohQX8uTPdRswEJONuN5A-bNv203d0N0b2BsJ7etx71g,2071
23
- pyobvector/schema/geo_srid_point.py,sha256=RwEoCgGTmXDc0le1B2E3mZudtqiFdMf2M0Va1ocmVSY,1210
24
- pyobvector/schema/gis_func.py,sha256=u7bqaB5qIylW8GvRdglLQL2H1SheQZNnAqgZrOGyrks,3118
25
- pyobvector/schema/match_against_func.py,sha256=ExTQJvAXHaZwBo1Sjy6IlnF1nF6D9xGUsF4f7zaP8Q0,1336
26
- pyobvector/schema/ob_table.py,sha256=wlb6Oo9LG-sr8XnG_wbX1Qi5CgnS0XUzNL5qTdsncoY,392
27
- pyobvector/schema/reflection.py,sha256=orA0_lCdcIHw2TumtRCrAH3zG2yAWrGjXOmK5mK9XPw,5903
28
- pyobvector/schema/replace_stmt.py,sha256=FtGLXHz6DwzD0FOZPn1EZgXdbHZu-K9HIHS02rZqYrE,560
29
- pyobvector/schema/sparse_vector.py,sha256=ojqUrvKUnxQE8FErB2B58KO540otOJBqiPTMlwcUW2M,1061
30
- pyobvector/schema/vec_dist_func.py,sha256=4GAWSrhFNDYooBpbBg604wDrByPrewp46Y4VeoDxV7Y,2986
31
- pyobvector/schema/vector.py,sha256=dFKfPcTOto0jNxVjhvDmJM7Q4wwp6Z-HcZ3K6oZxUMc,1120
32
- pyobvector/schema/vector_index.py,sha256=D1ZnhJEObWUCd9ESO57KN1ctl10tkEIH_gV0kwGrvu8,2250
33
- pyobvector/util/__init__.py,sha256=-opvZ4Ya0ByTAhls06547-zW3vkdYRkUH6W5OCKUHD4,388
34
- pyobvector/util/ob_version.py,sha256=cWkQWoJkIxG6OEF9-gBwJK8LUorltHuKSVAb_NFkpdE,1542
35
- pyobvector/util/sparse_vector.py,sha256=1IG0CRYfC2z39nGwuG43TImQkWiuPAXOlOnYqJ1hA-o,1275
36
- pyobvector/util/vector.py,sha256=58glSQqjOSTrGyNhUEIrs9r4F9oaYO_SdPNhMfbSnWs,2392
37
- pyobvector-0.2.19.dist-info/METADATA,sha256=9r91zFxzOPl3zzhSy521IZKsVWl0oXXzVx7ELQFjsV4,14310
38
- pyobvector-0.2.19.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
39
- pyobvector-0.2.19.dist-info/licenses/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
40
- pyobvector-0.2.19.dist-info/RECORD,,