pyobvector 0.2.20__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.
- pyobvector/client/collection_schema.py +2 -2
- pyobvector/client/exceptions.py +2 -2
- pyobvector/client/fts_index_param.py +39 -9
- pyobvector/client/hybrid_search.py +3 -3
- pyobvector/client/milvus_like_client.py +14 -14
- pyobvector/client/ob_client.py +9 -9
- pyobvector/client/ob_vec_client.py +15 -15
- pyobvector/client/ob_vec_json_table_client.py +17 -12
- pyobvector/client/partitions.py +17 -17
- pyobvector/json_table/json_value_returning_func.py +1 -2
- pyobvector/json_table/virtual_data_type.py +1 -1
- pyobvector/schema/array.py +5 -4
- pyobvector/schema/geo_srid_point.py +2 -2
- pyobvector/schema/gis_func.py +1 -2
- pyobvector/schema/reflection.py +2 -2
- pyobvector/util/ob_version.py +1 -2
- {pyobvector-0.2.20.dist-info → pyobvector-0.2.21.dist-info}/METADATA +15 -20
- {pyobvector-0.2.20.dist-info → pyobvector-0.2.21.dist-info}/RECORD +20 -20
- {pyobvector-0.2.20.dist-info → pyobvector-0.2.21.dist-info}/WHEEL +1 -1
- {pyobvector-0.2.20.dist-info → pyobvector-0.2.21.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""FieldSchema & CollectionSchema definition module to be compatible with Milvus."""
|
|
2
2
|
import copy
|
|
3
|
-
from typing import Optional
|
|
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[
|
|
128
|
+
fields: Optional[list[FieldSchema]] = None,
|
|
129
129
|
partitions: Optional[ObPartition] = None,
|
|
130
130
|
description: str = "", # ignored in oceanbase
|
|
131
131
|
**kwargs,
|
pyobvector/client/exceptions.py
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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:
|
|
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
|
|
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:
|
|
44
|
+
body: dict[str, Any],
|
|
45
45
|
**kwargs,
|
|
46
46
|
):
|
|
47
47
|
"""Execute hybrid search with parameter compatible with Elasticsearch.
|
|
@@ -66,7 +66,7 @@ class HybridSearch(Client):
|
|
|
66
66
|
def get_sql(
|
|
67
67
|
self,
|
|
68
68
|
index: str,
|
|
69
|
-
body:
|
|
69
|
+
body: dict[str, Any],
|
|
70
70
|
) -> str:
|
|
71
71
|
"""Get the SQL actually to be executed in hybrid search.
|
|
72
72
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Milvus Like Client."""
|
|
2
2
|
import logging
|
|
3
3
|
import json
|
|
4
|
-
from typing import Optional, Union
|
|
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
|
-
) ->
|
|
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[
|
|
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[
|
|
360
|
+
partition_names: Optional[list[str]] = None,
|
|
361
361
|
**kwargs, # pylint: disable=unused-argument
|
|
362
|
-
) ->
|
|
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[
|
|
470
|
+
output_fields: Optional[list[str]] = None,
|
|
471
471
|
timeout: Optional[float] = None, # pylint: disable=unused-argument
|
|
472
|
-
partition_names: Optional[
|
|
472
|
+
partition_names: Optional[list[str]] = None,
|
|
473
473
|
**kwargs, # pylint: disable=unused-argument
|
|
474
|
-
) ->
|
|
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[
|
|
536
|
+
output_fields: Optional[list[str]] = None,
|
|
537
537
|
timeout: Optional[float] = None, # pylint: disable=unused-argument
|
|
538
|
-
partition_names: Optional[
|
|
538
|
+
partition_names: Optional[list[str]] = None,
|
|
539
539
|
**kwargs, # pylint: disable=unused-argument
|
|
540
|
-
) ->
|
|
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[
|
|
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[
|
|
703
|
+
data: Union[dict, list[dict]],
|
|
704
704
|
timeout: Optional[float] = None, # pylint: disable=unused-argument
|
|
705
705
|
partition_name: Optional[str] = "",
|
|
706
|
-
) ->
|
|
706
|
+
) -> list[Union[str, int]]:
|
|
707
707
|
"""Update data in table. If primary key is duplicated, replace it.
|
|
708
708
|
|
|
709
709
|
Args:
|
pyobvector/client/ob_client.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import logging
|
|
2
|
-
from typing import
|
|
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:
|
|
145
|
-
indexes: Optional[
|
|
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[
|
|
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,
|
|
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[
|
|
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,
|
|
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[
|
|
369
|
-
partition_names: Optional[
|
|
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
|
|
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:
|
|
64
|
-
indexes: Optional[
|
|
63
|
+
columns: list[Column],
|
|
64
|
+
indexes: Optional[list[Index]] = None,
|
|
65
65
|
vidxs: Optional[IndexParams] = None,
|
|
66
|
-
fts_idxs: Optional[
|
|
66
|
+
fts_idxs: Optional[list[FtsIndexParam]] = None,
|
|
67
67
|
partitions: Optional[ObPartition] = None,
|
|
68
68
|
):
|
|
69
69
|
"""Create table with optional index_params.
|
|
@@ -140,7 +140,7 @@ class ObVecClient(ObClient):
|
|
|
140
140
|
table_name: str,
|
|
141
141
|
is_vec_index: bool,
|
|
142
142
|
index_name: str,
|
|
143
|
-
column_names:
|
|
143
|
+
column_names: list[str],
|
|
144
144
|
vidx_params: Optional[str] = None,
|
|
145
145
|
**kw,
|
|
146
146
|
):
|
|
@@ -274,12 +274,12 @@ class ObVecClient(ObClient):
|
|
|
274
274
|
distance_func,
|
|
275
275
|
with_dist: bool = False,
|
|
276
276
|
topk: int = 10,
|
|
277
|
-
output_column_names: Optional[
|
|
278
|
-
output_columns: Optional[Union[
|
|
279
|
-
extra_output_cols: Optional[
|
|
277
|
+
output_column_names: Optional[list[str]] = None,
|
|
278
|
+
output_columns: Optional[Union[list, tuple]] = None,
|
|
279
|
+
extra_output_cols: Optional[list] = None,
|
|
280
280
|
where_clause=None,
|
|
281
|
-
partition_names: Optional[
|
|
282
|
-
idx_name_hint: Optional[
|
|
281
|
+
partition_names: Optional[list[str]] = None,
|
|
282
|
+
idx_name_hint: Optional[list[str]] = None,
|
|
283
283
|
distance_threshold: Optional[float] = None,
|
|
284
284
|
**kwargs,
|
|
285
285
|
): # pylint: disable=unused-argument
|
|
@@ -403,11 +403,11 @@ class ObVecClient(ObClient):
|
|
|
403
403
|
distance_func,
|
|
404
404
|
with_dist: bool = False,
|
|
405
405
|
topk: int = 10,
|
|
406
|
-
output_column_names: Optional[
|
|
407
|
-
extra_output_cols: Optional[
|
|
406
|
+
output_column_names: Optional[list[str]] = None,
|
|
407
|
+
extra_output_cols: Optional[list] = None,
|
|
408
408
|
where_clause=None,
|
|
409
|
-
partition_names: Optional[
|
|
410
|
-
str_list: Optional[
|
|
409
|
+
partition_names: Optional[list[str]] = None,
|
|
410
|
+
str_list: Optional[list[str]] = None,
|
|
411
411
|
**kwargs,
|
|
412
412
|
): # pylint: disable=unused-argument
|
|
413
413
|
"""Perform post ann search.
|
|
@@ -483,7 +483,7 @@ class ObVecClient(ObClient):
|
|
|
483
483
|
vec_column_name: str,
|
|
484
484
|
distance_func,
|
|
485
485
|
topk: int = 10,
|
|
486
|
-
output_column_names: Optional[
|
|
486
|
+
output_column_names: Optional[list[str]] = None,
|
|
487
487
|
where_clause=None,
|
|
488
488
|
**kwargs,
|
|
489
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
|
|
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:
|
|
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[
|
|
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) ->
|
|
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
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
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}'"
|
pyobvector/client/partitions.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
"""A module to do compilation of OceanBase Parition Clause."""
|
|
2
|
-
from typing import
|
|
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[
|
|
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,
|
|
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:
|
|
90
|
+
range_part_infos: list[RangeListPartInfo],
|
|
91
91
|
range_expr: Optional[str] = None,
|
|
92
|
-
col_name_list: Optional[
|
|
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:
|
|
143
|
+
range_part_infos: list[RangeListPartInfo],
|
|
144
144
|
range_expr: Optional[str] = None,
|
|
145
|
-
col_name_list: Optional[
|
|
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:
|
|
179
|
+
list_part_infos: list[RangeListPartInfo],
|
|
180
180
|
list_expr: Optional[str] = None,
|
|
181
|
-
col_name_list: Optional[
|
|
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:
|
|
231
|
+
list_part_infos: list[RangeListPartInfo],
|
|
232
232
|
list_expr: Optional[str] = None,
|
|
233
|
-
col_name_list: Optional[
|
|
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:
|
|
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:
|
|
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:
|
|
338
|
-
key_part_name_list:
|
|
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:
|
|
385
|
-
key_part_name_list:
|
|
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
|
|
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
|
|
5
|
+
from typing import Annotated
|
|
6
6
|
|
|
7
7
|
from pydantic import BaseModel, Field, AfterValidator, create_model
|
|
8
8
|
|
pyobvector/schema/array.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""ARRAY: An extended data type for SQLAlchemy"""
|
|
2
2
|
import json
|
|
3
|
-
from typing import Any,
|
|
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 {}"
|
|
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[
|
|
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
|
|
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:
|
|
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
|
|
pyobvector/schema/gis_func.py
CHANGED
|
@@ -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,
|
|
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
|
):
|
pyobvector/schema/reflection.py
CHANGED
|
@@ -35,12 +35,12 @@ class OceanBaseTableDefinitionParser(MySQLTableDefinitionParser):
|
|
|
35
35
|
|
|
36
36
|
self._re_array_column = _re_compile(
|
|
37
37
|
r"\s*"
|
|
38
|
-
r"
|
|
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*$"
|
|
43
|
+
r"\s*,?\s*$".format(**quotes)
|
|
44
44
|
)
|
|
45
45
|
|
|
46
46
|
self._re_key = _re_compile(
|
pyobvector/util/ob_version.py
CHANGED
|
@@ -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:
|
|
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.
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
Requires-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
|
|
31
|
+
uv sync
|
|
36
32
|
```
|
|
37
33
|
|
|
38
34
|
- install with pip:
|
|
39
35
|
|
|
40
36
|
```shell
|
|
41
|
-
pip install pyobvector==0.2.
|
|
37
|
+
pip install pyobvector==0.2.21
|
|
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=
|
|
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=
|
|
6
|
-
pyobvector/client/fts_index_param.py,sha256=
|
|
7
|
-
pyobvector/client/hybrid_search.py,sha256=
|
|
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
8
|
pyobvector/client/index_param.py,sha256=Tbu_-qg4SIXIzn1qpTg8Zl1L_RCXCUAEsZPKsA0LsNY,7062
|
|
9
|
-
pyobvector/client/milvus_like_client.py,sha256=
|
|
10
|
-
pyobvector/client/ob_client.py,sha256=
|
|
11
|
-
pyobvector/client/ob_vec_client.py,sha256=
|
|
12
|
-
pyobvector/client/ob_vec_json_table_client.py,sha256=
|
|
13
|
-
pyobvector/client/partitions.py,sha256=
|
|
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
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=
|
|
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=
|
|
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=
|
|
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=
|
|
24
|
-
pyobvector/schema/gis_func.py,sha256=
|
|
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=
|
|
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=
|
|
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.
|
|
38
|
-
pyobvector-0.2.
|
|
39
|
-
pyobvector-0.2.
|
|
40
|
-
pyobvector-0.2.
|
|
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,,
|
|
File without changes
|