pyobvector 0.2.20__py3-none-any.whl → 0.2.22__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
 
@@ -41,7 +41,7 @@ class HybridSearch(Client):
41
41
  def search(
42
42
  self,
43
43
  index: str,
44
- body: Dict[str, Any],
44
+ body: dict[str, Any],
45
45
  **kwargs,
46
46
  ):
47
47
  """Execute hybrid search with parameter compatible with Elasticsearch.
@@ -61,12 +61,14 @@ class HybridSearch(Client):
61
61
  with self.engine.connect() as conn:
62
62
  with conn.begin():
63
63
  res = conn.execute(sql, {"index": index, "body_str": body_str}).fetchone()
64
+ if res[0] is None:
65
+ return []
64
66
  return json.loads(res[0])
65
67
 
66
68
  def get_sql(
67
69
  self,
68
70
  index: str,
69
- body: Dict[str, Any],
71
+ body: dict[str, Any],
70
72
  ) -> str:
71
73
  """Get the SQL actually to be executed in hybrid search.
72
74
 
@@ -84,4 +86,6 @@ class HybridSearch(Client):
84
86
  with self.engine.connect() as conn:
85
87
  with conn.begin():
86
88
  res = conn.execute(sql, {"index": index, "body_str": body_str}).fetchone()
89
+ if res[0] is None:
90
+ return ""
87
91
  return res[0]
@@ -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
@@ -141,8 +141,8 @@ class ObClient:
141
141
  def create_table(
142
142
  self,
143
143
  table_name: str,
144
- columns: List[Column],
145
- indexes: Optional[List[Index]] = None,
144
+ columns: list[Column],
145
+ indexes: Optional[list[Index]] = None,
146
146
  partitions: Optional[ObPartition] = None,
147
147
  **kwargs,
148
148
  ):
@@ -208,7 +208,7 @@ class ObClient:
208
208
  def insert(
209
209
  self,
210
210
  table_name: str,
211
- data: Union[Dict, List[Dict]],
211
+ data: Union[dict, list[dict]],
212
212
  partition_name: Optional[str] = "",
213
213
  ):
214
214
  """Insert data into table.
@@ -218,7 +218,7 @@ class ObClient:
218
218
  data (Union[Dict, List[Dict]]): data that will be inserted
219
219
  partition_name (Optional[str]): limit the query to certain partition
220
220
  """
221
- if isinstance(data, Dict):
221
+ if isinstance(data, dict):
222
222
  data = [data]
223
223
 
224
224
  if len(data) == 0:
@@ -240,7 +240,7 @@ class ObClient:
240
240
  def upsert(
241
241
  self,
242
242
  table_name: str,
243
- data: Union[Dict, List[Dict]],
243
+ data: Union[dict, list[dict]],
244
244
  partition_name: Optional[str] = "",
245
245
  ):
246
246
  """Update data in table. If primary key is duplicated, replace it.
@@ -250,7 +250,7 @@ class ObClient:
250
250
  data (Union[Dict, List[Dict]]): data that will be upserted
251
251
  partition_name (Optional[str]): limit the query to certain partition
252
252
  """
253
- if isinstance(data, Dict):
253
+ if isinstance(data, dict):
254
254
  data = [data]
255
255
 
256
256
  if len(data) == 0:
@@ -365,8 +365,8 @@ class ObClient:
365
365
  table_name: str,
366
366
  ids: Optional[Union[list, str, int]] = None,
367
367
  where_clause=None,
368
- output_column_name: Optional[List[str]] = None,
369
- partition_names: Optional[List[str]] = None,
368
+ output_column_name: Optional[list[str]] = None,
369
+ partition_names: Optional[list[str]] = None,
370
370
  n_limits: Optional[int] = None,
371
371
  ):
372
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,11 +60,12 @@ 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
+ **kwargs,
68
69
  ):
69
70
  """Create table with optional index_params.
70
71
 
@@ -75,8 +76,10 @@ class ObVecClient(ObClient):
75
76
  vidxs (Optional[IndexParams]): optional vector index schema
76
77
  fts_idxs (Optional[List[FtsIndexParam]]): optional full-text search index schema
77
78
  partitions (Optional[ObPartition]): optional partition strategy
79
+ **kwargs: additional keyword arguments (e.g., mysql_organization='heap')
78
80
  """
79
81
  sparse_vidxs = self._get_sparse_vector_index_params(vidxs)
82
+ kwargs.setdefault("extend_existing", True)
80
83
  with self.engine.connect() as conn:
81
84
  with conn.begin():
82
85
  # create table with common index
@@ -86,14 +89,14 @@ class ObVecClient(ObClient):
86
89
  self.metadata_obj,
87
90
  *columns,
88
91
  *indexes,
89
- extend_existing=True,
92
+ **kwargs,
90
93
  )
91
94
  else:
92
95
  table = ObTable(
93
96
  table_name,
94
97
  self.metadata_obj,
95
98
  *columns,
96
- extend_existing=True,
99
+ **kwargs,
97
100
  )
98
101
  if sparse_vidxs is not None and len(sparse_vidxs) > 0:
99
102
  create_table_sql = str(CreateTable(table).compile(self.engine))
@@ -140,7 +143,7 @@ class ObVecClient(ObClient):
140
143
  table_name: str,
141
144
  is_vec_index: bool,
142
145
  index_name: str,
143
- column_names: List[str],
146
+ column_names: list[str],
144
147
  vidx_params: Optional[str] = None,
145
148
  **kw,
146
149
  ):
@@ -274,12 +277,12 @@ class ObVecClient(ObClient):
274
277
  distance_func,
275
278
  with_dist: bool = False,
276
279
  topk: int = 10,
277
- output_column_names: Optional[List[str]] = None,
278
- output_columns: Optional[Union[List, tuple]] = None,
279
- extra_output_cols: Optional[List] = None,
280
+ output_column_names: Optional[list[str]] = None,
281
+ output_columns: Optional[Union[list, tuple]] = None,
282
+ extra_output_cols: Optional[list] = None,
280
283
  where_clause=None,
281
- partition_names: Optional[List[str]] = None,
282
- idx_name_hint: Optional[List[str]] = None,
284
+ partition_names: Optional[list[str]] = None,
285
+ idx_name_hint: Optional[list[str]] = None,
283
286
  distance_threshold: Optional[float] = None,
284
287
  **kwargs,
285
288
  ): # pylint: disable=unused-argument
@@ -403,11 +406,11 @@ class ObVecClient(ObClient):
403
406
  distance_func,
404
407
  with_dist: bool = False,
405
408
  topk: int = 10,
406
- output_column_names: Optional[List[str]] = None,
407
- extra_output_cols: Optional[List] = None,
409
+ output_column_names: Optional[list[str]] = None,
410
+ extra_output_cols: Optional[list] = None,
408
411
  where_clause=None,
409
- partition_names: Optional[List[str]] = None,
410
- str_list: Optional[List[str]] = None,
412
+ partition_names: Optional[list[str]] = None,
413
+ str_list: Optional[list[str]] = None,
411
414
  **kwargs,
412
415
  ): # pylint: disable=unused-argument
413
416
  """Perform post ann search.
@@ -483,7 +486,7 @@ class ObVecClient(ObClient):
483
486
  vec_column_name: str,
484
487
  distance_func,
485
488
  topk: int = 10,
486
- output_column_names: Optional[List[str]] = None,
489
+ output_column_names: Optional[list[str]] = None,
487
490
  where_clause=None,
488
491
  **kwargs,
489
492
  ): # 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:
@@ -884,12 +882,19 @@ class ObVecJsonTableClient(ObVecClient):
884
882
  identifier.args['quoted'] = False
885
883
  col.args['table'] = identifier
886
884
 
887
- join_clause = parse_one(f"from t1, {json_table_str}")
888
- join_node = join_clause.args['joins'][0]
889
- if 'joins' in ast.args.keys():
890
- ast.args['joins'].append(join_node)
891
- else:
892
- 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)
893
898
 
894
899
  if real_user_id:
895
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.20
3
+ Version: 0.2.22
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.20
37
+ pip install pyobvector==0.2.22
42
38
  ```
43
39
 
44
40
  ## Build Doc
@@ -454,4 +450,3 @@ sql = client.get_sql(index=test_table_name, body=body)
454
450
  print(sql) # prints the SQL query
455
451
  ```
456
452
 
457
-
@@ -1,40 +1,40 @@
1
1
  pyobvector/__init__.py,sha256=5RtWI_ol3qxj_m7UXTQzByTmGZdIiQ0yyCyHZUhRDas,3888
2
2
  pyobvector/client/__init__.py,sha256=fDK2FVdSm3-XCwTqsam7zisic5UMhANUq97r29i27nc,2819
3
- pyobvector/client/collection_schema.py,sha256=a7JQk83ZxMsvMDGt5CC_4lz2-skONqKgk-OGUz297hM,5538
3
+ pyobvector/client/collection_schema.py,sha256=lQTlanxhfZ0W74HUEB7XwvnkwJASxNSsxurXCvMv8nw,5532
4
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=wXHu4iOBRyVuMByiH3XyB4kKYqvzmVBSSiod5lj7cVY,2571
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=HOva3UMuNsPUJPoL3jyH9YXoeCp1FgFPqumJ6XOE44U,2695
8
8
  pyobvector/client/index_param.py,sha256=Tbu_-qg4SIXIzn1qpTg8Zl1L_RCXCUAEsZPKsA0LsNY,7062
9
- pyobvector/client/milvus_like_client.py,sha256=jHxB-ZmIGZiIDxEpurSlAKffiCF_KFNZP-14_vq1RQM,27887
10
- pyobvector/client/ob_client.py,sha256=xfLbkTJrrmKArUJSLyfUcI_CsMk26KLB2DVFb6Y1-OQ,16987
11
- pyobvector/client/ob_vec_client.py,sha256=jtAs2pB987zYfwyFgaipv8TgYsDfiFqeBDU7PKiUFbs,20241
12
- pyobvector/client/ob_vec_json_table_client.py,sha256=YwAKG1pGAYgbhaYB6wDCdxB6L4uJcXCHCwLTy99ZirY,39312
13
- pyobvector/client/partitions.py,sha256=Bxwr5yVNlXwZc7SXBC03NeqL9giy4Fe6S2qZdHD8xGw,15621
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=k7yRsc2u51kpLQGAxa4dFaKCZRowcqUdZYITVQwiu6Q,20365
12
+ pyobvector/client/ob_vec_json_table_client.py,sha256=NFj7-1AQpTMk5XllecAgTKSGAq8qMIa36FM2cuhd_fQ,39605
13
+ pyobvector/client/partitions.py,sha256=CfkSe0ftb0jbdgdvNF9_SvXxcDhpOoS3pe7ezMeNI0c,15615
14
14
  pyobvector/client/schema_type.py,sha256=gH2YiBsgkryo-R0GB_NYuRXMvzvrSjOamZTy6pwn2n0,1673
15
15
  pyobvector/json_table/__init__.py,sha256=X5MmK3f10oyJleUUFZJFeunMEfzmf6P1f_7094b-FZc,554
16
- pyobvector/json_table/json_value_returning_func.py,sha256=NWSV2zhe2-1KhIprQaFqOH3vUVF46YaHIZUqX66WZKM,1864
16
+ pyobvector/json_table/json_value_returning_func.py,sha256=fW5Rk5vrVfyTPJPciPyb64A0CqSJBwBS-PYbcS9LhOY,1815
17
17
  pyobvector/json_table/oceanbase_dialect.py,sha256=lxpbWBQdK18LWXLmGyk_-ODv6VfnwGLHbcpsQMElOUo,4480
18
- pyobvector/json_table/virtual_data_type.py,sha256=uQh6ZQ0UbwpVO9TFegGeu4E8bXW7rdLHAXFQJdiEjLs,3467
18
+ pyobvector/json_table/virtual_data_type.py,sha256=ALffzZgByEwMsC-JdL_iEW3kwJcfqNGaNSIY645oUfo,3456
19
19
  pyobvector/schema/__init__.py,sha256=OMn7Cd2E8o_tm2ArbAXS_zRbiDW2sj7YKPLnbpmaueg,2405
20
- pyobvector/schema/array.py,sha256=WDWLZbCdu8stK8wlGWfKUjkhWifS8vbsfYUEEJsQOlQ,4163
20
+ pyobvector/schema/array.py,sha256=YiGIyALN1_XxWuz9YyuiqhpyRN1DIu9n286vq6O09_U,4181
21
21
  pyobvector/schema/dialect.py,sha256=6D9A7Niqig2mwK7C8sP7mCP7kYr5be2jV0xqL87mlz4,1999
22
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
23
+ pyobvector/schema/geo_srid_point.py,sha256=BZuiySoemvtLBPYoZ_BRQT4Gd7fcU1C2xdQfkxpb0OQ,1203
24
+ pyobvector/schema/gis_func.py,sha256=sDhD701I45sRCXNY_E20ujjxe0ldAXofd6kTAhMW12g,3093
25
25
  pyobvector/schema/match_against_func.py,sha256=ExTQJvAXHaZwBo1Sjy6IlnF1nF6D9xGUsF4f7zaP8Q0,1336
26
26
  pyobvector/schema/ob_table.py,sha256=wlb6Oo9LG-sr8XnG_wbX1Qi5CgnS0XUzNL5qTdsncoY,392
27
- pyobvector/schema/reflection.py,sha256=orA0_lCdcIHw2TumtRCrAH3zG2yAWrGjXOmK5mK9XPw,5903
27
+ pyobvector/schema/reflection.py,sha256=guS74lW1wzBOCv5FWeXS1a_KnS-5Md9C_dYaNqiK2yA,5903
28
28
  pyobvector/schema/replace_stmt.py,sha256=FtGLXHz6DwzD0FOZPn1EZgXdbHZu-K9HIHS02rZqYrE,560
29
29
  pyobvector/schema/sparse_vector.py,sha256=ojqUrvKUnxQE8FErB2B58KO540otOJBqiPTMlwcUW2M,1061
30
30
  pyobvector/schema/vec_dist_func.py,sha256=4GAWSrhFNDYooBpbBg604wDrByPrewp46Y4VeoDxV7Y,2986
31
31
  pyobvector/schema/vector.py,sha256=dFKfPcTOto0jNxVjhvDmJM7Q4wwp6Z-HcZ3K6oZxUMc,1120
32
32
  pyobvector/schema/vector_index.py,sha256=D1ZnhJEObWUCd9ESO57KN1ctl10tkEIH_gV0kwGrvu8,2250
33
33
  pyobvector/util/__init__.py,sha256=-opvZ4Ya0ByTAhls06547-zW3vkdYRkUH6W5OCKUHD4,388
34
- pyobvector/util/ob_version.py,sha256=cWkQWoJkIxG6OEF9-gBwJK8LUorltHuKSVAb_NFkpdE,1542
34
+ pyobvector/util/ob_version.py,sha256=69me12REWn_T6oi7XjVAF_Dfq_iiaEVZjiHdjog57zs,1518
35
35
  pyobvector/util/sparse_vector.py,sha256=1IG0CRYfC2z39nGwuG43TImQkWiuPAXOlOnYqJ1hA-o,1275
36
36
  pyobvector/util/vector.py,sha256=58glSQqjOSTrGyNhUEIrs9r4F9oaYO_SdPNhMfbSnWs,2392
37
- pyobvector-0.2.20.dist-info/METADATA,sha256=f8EONRGcUmb7zDKtSU6R-axq16x4SKLh4okd9vwT5Cg,14317
38
- pyobvector-0.2.20.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
39
- pyobvector-0.2.20.dist-info/licenses/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
40
- pyobvector-0.2.20.dist-info/RECORD,,
37
+ pyobvector-0.2.22.dist-info/METADATA,sha256=j272Pr5O4p-Ce2rxA12LAkj2jziRwV6MbqRxhOtB74U,14120
38
+ pyobvector-0.2.22.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
39
+ pyobvector-0.2.22.dist-info/licenses/LICENSE,sha256=WNHhf_5RCaeuKWyq_K39vmp9F28LxKsB4SpomwSZ2L0,11357
40
+ pyobvector-0.2.22.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