cocoindex 0.1.36__cp312-cp312-macosx_11_0_arm64.whl → 0.1.37__cp312-cp312-macosx_11_0_arm64.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.
- cocoindex/_engine.cpython-312-darwin.so +0 -0
- cocoindex/cli.py +3 -2
- cocoindex/convert.py +33 -11
- cocoindex/sources.py +13 -0
- cocoindex/tests/test_convert.py +70 -3
- cocoindex/typing.py +35 -17
- {cocoindex-0.1.36.dist-info → cocoindex-0.1.37.dist-info}/METADATA +2 -2
- {cocoindex-0.1.36.dist-info → cocoindex-0.1.37.dist-info}/RECORD +10 -10
- {cocoindex-0.1.36.dist-info → cocoindex-0.1.37.dist-info}/WHEEL +1 -1
- {cocoindex-0.1.36.dist-info → cocoindex-0.1.37.dist-info}/licenses/LICENSE +0 -0
Binary file
|
cocoindex/cli.py
CHANGED
@@ -212,11 +212,12 @@ def server(address: str | None, live_update: bool, quiet: bool, cors_origin: str
|
|
212
212
|
|
213
213
|
lib.start_server(server_settings)
|
214
214
|
|
215
|
+
if COCOINDEX_HOST in cors_origins:
|
216
|
+
click.echo(f"Open CocoInsight at: {COCOINDEX_HOST}/cocoinsight")
|
217
|
+
|
215
218
|
if live_update:
|
216
219
|
options = flow.FlowLiveUpdaterOptions(live_mode=True, print_stats=not quiet)
|
217
220
|
flow.update_all_flows(options)
|
218
|
-
if COCOINDEX_HOST in cors_origins:
|
219
|
-
click.echo(f"Open CocoInsight at: {COCOINDEX_HOST}/cocoinsight")
|
220
221
|
input("Press Enter to stop...")
|
221
222
|
|
222
223
|
|
cocoindex/convert.py
CHANGED
@@ -8,13 +8,15 @@ import uuid
|
|
8
8
|
|
9
9
|
from enum import Enum
|
10
10
|
from typing import Any, Callable, get_origin
|
11
|
-
from .typing import analyze_type_info, encode_enriched_type, TABLE_TYPES, KEY_FIELD_NAME
|
11
|
+
from .typing import analyze_type_info, encode_enriched_type, is_namedtuple_type, TABLE_TYPES, KEY_FIELD_NAME
|
12
12
|
|
13
13
|
|
14
14
|
def encode_engine_value(value: Any) -> Any:
|
15
15
|
"""Encode a Python value to an engine value."""
|
16
16
|
if dataclasses.is_dataclass(value):
|
17
17
|
return [encode_engine_value(getattr(value, f.name)) for f in dataclasses.fields(value)]
|
18
|
+
if is_namedtuple_type(type(value)):
|
19
|
+
return [encode_engine_value(getattr(value, name)) for name in value._fields]
|
18
20
|
if isinstance(value, (list, tuple)):
|
19
21
|
return [encode_engine_value(v) for v in value]
|
20
22
|
if isinstance(value, dict):
|
@@ -55,16 +57,16 @@ def make_engine_value_decoder(
|
|
55
57
|
f"Type mismatch for `{''.join(field_path)}`: "
|
56
58
|
f"passed in {src_type_kind}, declared {dst_annotation} ({dst_type_info.kind})")
|
57
59
|
|
58
|
-
if dst_type_info.
|
60
|
+
if dst_type_info.struct_type is not None:
|
59
61
|
return _make_engine_struct_value_decoder(
|
60
|
-
field_path, src_type['fields'], dst_type_info.
|
62
|
+
field_path, src_type['fields'], dst_type_info.struct_type)
|
61
63
|
|
62
64
|
if src_type_kind in TABLE_TYPES:
|
63
65
|
field_path.append('[*]')
|
64
66
|
elem_type_info = analyze_type_info(dst_type_info.elem_type)
|
65
|
-
if elem_type_info.
|
67
|
+
if elem_type_info.struct_type is None:
|
66
68
|
raise ValueError(f"Type mismatch for `{''.join(field_path)}`: "
|
67
|
-
f"declared `{dst_type_info.kind}`, a dataclass type expected")
|
69
|
+
f"declared `{dst_type_info.kind}`, a dataclass or NamedTuple type expected")
|
68
70
|
engine_fields_schema = src_type['row']['fields']
|
69
71
|
if elem_type_info.key_type is not None:
|
70
72
|
key_field_schema = engine_fields_schema[0]
|
@@ -73,14 +75,14 @@ def make_engine_value_decoder(
|
|
73
75
|
field_path, key_field_schema['type'], elem_type_info.key_type)
|
74
76
|
field_path.pop()
|
75
77
|
value_decoder = _make_engine_struct_value_decoder(
|
76
|
-
field_path, engine_fields_schema[1:], elem_type_info.
|
78
|
+
field_path, engine_fields_schema[1:], elem_type_info.struct_type)
|
77
79
|
def decode(value):
|
78
80
|
if value is None:
|
79
81
|
return None
|
80
82
|
return {key_decoder(v[0]): value_decoder(v[1:]) for v in value}
|
81
83
|
else:
|
82
84
|
elem_decoder = _make_engine_struct_value_decoder(
|
83
|
-
field_path, engine_fields_schema, elem_type_info.
|
85
|
+
field_path, engine_fields_schema, elem_type_info.struct_type)
|
84
86
|
def decode(value):
|
85
87
|
if value is None:
|
86
88
|
return None
|
@@ -96,11 +98,31 @@ def make_engine_value_decoder(
|
|
96
98
|
def _make_engine_struct_value_decoder(
|
97
99
|
field_path: list[str],
|
98
100
|
src_fields: list[dict[str, Any]],
|
99
|
-
|
101
|
+
dst_struct_type: type,
|
100
102
|
) -> Callable[[list], Any]:
|
101
103
|
"""Make a decoder from an engine field values to a Python value."""
|
102
104
|
|
103
105
|
src_name_to_idx = {f['name']: i for i, f in enumerate(src_fields)}
|
106
|
+
|
107
|
+
is_dataclass = dataclasses.is_dataclass(dst_struct_type)
|
108
|
+
is_namedtuple = is_namedtuple_type(dst_struct_type)
|
109
|
+
|
110
|
+
if is_dataclass:
|
111
|
+
parameters = inspect.signature(dst_struct_type).parameters
|
112
|
+
elif is_namedtuple:
|
113
|
+
defaults = getattr(dst_struct_type, '_field_defaults', {})
|
114
|
+
parameters = {
|
115
|
+
name: inspect.Parameter(
|
116
|
+
name=name,
|
117
|
+
kind=inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
118
|
+
default=defaults.get(name, inspect.Parameter.empty),
|
119
|
+
annotation=dst_struct_type.__annotations__.get(name, inspect.Parameter.empty)
|
120
|
+
)
|
121
|
+
for name in dst_struct_type._fields
|
122
|
+
}
|
123
|
+
else:
|
124
|
+
raise ValueError(f"Unsupported struct type: {dst_struct_type}")
|
125
|
+
|
104
126
|
def make_closure_for_value(name: str, param: inspect.Parameter) -> Callable[[list], Any]:
|
105
127
|
src_idx = src_name_to_idx.get(name)
|
106
128
|
if src_idx is not None:
|
@@ -108,7 +130,7 @@ def _make_engine_struct_value_decoder(
|
|
108
130
|
field_decoder = make_engine_value_decoder(
|
109
131
|
field_path, src_fields[src_idx]['type'], param.annotation)
|
110
132
|
field_path.pop()
|
111
|
-
return lambda values: field_decoder(values[src_idx])
|
133
|
+
return lambda values: field_decoder(values[src_idx]) if len(values) > src_idx else param.default
|
112
134
|
|
113
135
|
default_value = param.default
|
114
136
|
if default_value is inspect.Parameter.empty:
|
@@ -119,9 +141,9 @@ def _make_engine_struct_value_decoder(
|
|
119
141
|
|
120
142
|
field_value_decoder = [
|
121
143
|
make_closure_for_value(name, param)
|
122
|
-
for (name, param) in
|
144
|
+
for (name, param) in parameters.items()]
|
123
145
|
|
124
|
-
return lambda values:
|
146
|
+
return lambda values: dst_struct_type(
|
125
147
|
*(decoder(values) for decoder in field_value_decoder))
|
126
148
|
|
127
149
|
def dump_engine_object(v: Any) -> Any:
|
cocoindex/sources.py
CHANGED
@@ -28,3 +28,16 @@ class GoogleDrive(op.SourceSpec):
|
|
28
28
|
root_folder_ids: list[str]
|
29
29
|
binary: bool = False
|
30
30
|
recent_changes_poll_interval: datetime.timedelta | None = None
|
31
|
+
|
32
|
+
|
33
|
+
class AmazonS3(op.SourceSpec):
|
34
|
+
"""Import data from an Amazon S3 bucket. Supports optional prefix and file filtering by glob patterns."""
|
35
|
+
|
36
|
+
_op_category = op.OpCategory.SOURCE
|
37
|
+
|
38
|
+
bucket_name: str
|
39
|
+
prefix: str | None = None
|
40
|
+
binary: bool = False
|
41
|
+
included_patterns: list[str] | None = None
|
42
|
+
excluded_patterns: list[str] | None = None
|
43
|
+
sqs_queue_url: str | None = None
|
cocoindex/tests/test_convert.py
CHANGED
@@ -1,11 +1,12 @@
|
|
1
1
|
import uuid
|
2
2
|
import datetime
|
3
3
|
from dataclasses import dataclass, make_dataclass
|
4
|
+
from typing import NamedTuple, Literal
|
4
5
|
import pytest
|
5
6
|
import cocoindex
|
6
7
|
from cocoindex.typing import encode_enriched_type
|
7
8
|
from cocoindex.convert import encode_engine_value, make_engine_value_decoder
|
8
|
-
|
9
|
+
|
9
10
|
@dataclass
|
10
11
|
class Order:
|
11
12
|
order_id: str
|
@@ -33,6 +34,17 @@ class NestedStruct:
|
|
33
34
|
orders: list[Order]
|
34
35
|
count: int = 0
|
35
36
|
|
37
|
+
class OrderNamedTuple(NamedTuple):
|
38
|
+
order_id: str
|
39
|
+
name: str
|
40
|
+
price: float
|
41
|
+
extra_field: str = "default_extra"
|
42
|
+
|
43
|
+
class CustomerNamedTuple(NamedTuple):
|
44
|
+
name: str
|
45
|
+
order: OrderNamedTuple
|
46
|
+
tags: list[Tag] | None = None
|
47
|
+
|
36
48
|
def build_engine_value_decoder(engine_type_in_py, python_type=None):
|
37
49
|
"""
|
38
50
|
Helper to build a converter for the given engine-side type (as represented in Python).
|
@@ -62,10 +74,16 @@ def test_encode_engine_value_date_time_types():
|
|
62
74
|
def test_encode_engine_value_struct():
|
63
75
|
order = Order(order_id="O123", name="mixed nuts", price=25.0)
|
64
76
|
assert encode_engine_value(order) == ["O123", "mixed nuts", 25.0, "default_extra"]
|
77
|
+
|
78
|
+
order_nt = OrderNamedTuple(order_id="O123", name="mixed nuts", price=25.0)
|
79
|
+
assert encode_engine_value(order_nt) == ["O123", "mixed nuts", 25.0, "default_extra"]
|
65
80
|
|
66
81
|
def test_encode_engine_value_list_of_structs():
|
67
82
|
orders = [Order("O1", "item1", 10.0), Order("O2", "item2", 20.0)]
|
68
83
|
assert encode_engine_value(orders) == [["O1", "item1", 10.0, "default_extra"], ["O2", "item2", 20.0, "default_extra"]]
|
84
|
+
|
85
|
+
orders_nt = [OrderNamedTuple("O1", "item1", 10.0), OrderNamedTuple("O2", "item2", 20.0)]
|
86
|
+
assert encode_engine_value(orders_nt) == [["O1", "item1", 10.0, "default_extra"], ["O2", "item2", 20.0, "default_extra"]]
|
69
87
|
|
70
88
|
def test_encode_engine_value_struct_with_list():
|
71
89
|
basket = Basket(items=["apple", "banana"])
|
@@ -74,6 +92,9 @@ def test_encode_engine_value_struct_with_list():
|
|
74
92
|
def test_encode_engine_value_nested_struct():
|
75
93
|
customer = Customer(name="Alice", order=Order("O1", "item1", 10.0))
|
76
94
|
assert encode_engine_value(customer) == ["Alice", ["O1", "item1", 10.0, "default_extra"], None]
|
95
|
+
|
96
|
+
customer_nt = CustomerNamedTuple(name="Alice", order=OrderNamedTuple("O1", "item1", 10.0))
|
97
|
+
assert encode_engine_value(customer_nt) == ["Alice", ["O1", "item1", 10.0, "default_extra"], None]
|
77
98
|
|
78
99
|
def test_encode_engine_value_empty_list():
|
79
100
|
assert encode_engine_value([]) == []
|
@@ -103,20 +124,34 @@ def test_make_engine_value_decoder_basic_types():
|
|
103
124
|
@pytest.mark.parametrize(
|
104
125
|
"data_type, engine_val, expected",
|
105
126
|
[
|
106
|
-
# All fields match
|
127
|
+
# All fields match (dataclass)
|
107
128
|
(Order, ["O123", "mixed nuts", 25.0, "default_extra"], Order("O123", "mixed nuts", 25.0, "default_extra")),
|
129
|
+
# All fields match (NamedTuple)
|
130
|
+
(OrderNamedTuple, ["O123", "mixed nuts", 25.0, "default_extra"], OrderNamedTuple("O123", "mixed nuts", 25.0, "default_extra")),
|
108
131
|
# Extra field in engine value (should ignore extra)
|
109
132
|
(Order, ["O123", "mixed nuts", 25.0, "default_extra", "unexpected"], Order("O123", "mixed nuts", 25.0, "default_extra")),
|
133
|
+
(OrderNamedTuple, ["O123", "mixed nuts", 25.0, "default_extra", "unexpected"], OrderNamedTuple("O123", "mixed nuts", 25.0, "default_extra")),
|
110
134
|
# Fewer fields in engine value (should fill with default)
|
111
135
|
(Order, ["O123", "mixed nuts", 0.0, "default_extra"], Order("O123", "mixed nuts", 0.0, "default_extra")),
|
136
|
+
(OrderNamedTuple, ["O123", "mixed nuts", 0.0, "default_extra"], OrderNamedTuple("O123", "mixed nuts", 0.0, "default_extra")),
|
112
137
|
# More fields in engine value (should ignore extra)
|
113
138
|
(Order, ["O123", "mixed nuts", 25.0, "unexpected"], Order("O123", "mixed nuts", 25.0, "unexpected")),
|
139
|
+
(OrderNamedTuple, ["O123", "mixed nuts", 25.0, "unexpected"], OrderNamedTuple("O123", "mixed nuts", 25.0, "unexpected")),
|
114
140
|
# Truly extra field (should ignore the fifth field)
|
115
141
|
(Order, ["O123", "mixed nuts", 25.0, "default_extra", "ignored"], Order("O123", "mixed nuts", 25.0, "default_extra")),
|
142
|
+
(OrderNamedTuple, ["O123", "mixed nuts", 25.0, "default_extra", "ignored"], OrderNamedTuple("O123", "mixed nuts", 25.0, "default_extra")),
|
116
143
|
# Missing optional field in engine value (tags=None)
|
117
144
|
(Customer, ["Alice", ["O1", "item1", 10.0, "default_extra"], None], Customer("Alice", Order("O1", "item1", 10.0, "default_extra"), None)),
|
145
|
+
(CustomerNamedTuple, ["Alice", ["O1", "item1", 10.0, "default_extra"], None], CustomerNamedTuple("Alice", OrderNamedTuple("O1", "item1", 10.0, "default_extra"), None)),
|
118
146
|
# Extra field in engine value for Customer (should ignore)
|
119
147
|
(Customer, ["Alice", ["O1", "item1", 10.0, "default_extra"], [["vip"]], "extra"], Customer("Alice", Order("O1", "item1", 10.0, "default_extra"), [Tag("vip")])),
|
148
|
+
(CustomerNamedTuple, ["Alice", ["O1", "item1", 10.0, "default_extra"], [["vip"]], "extra"], CustomerNamedTuple("Alice", OrderNamedTuple("O1", "item1", 10.0, "default_extra"), [Tag("vip")])),
|
149
|
+
# Missing optional field with default
|
150
|
+
(Order, ["O123", "mixed nuts", 25.0], Order("O123", "mixed nuts", 25.0, "default_extra")),
|
151
|
+
(OrderNamedTuple, ["O123", "mixed nuts", 25.0], OrderNamedTuple("O123", "mixed nuts", 25.0, "default_extra")),
|
152
|
+
# Partial optional fields
|
153
|
+
(Customer, ["Alice", ["O1", "item1", 10.0]], Customer("Alice", Order("O1", "item1", 10.0, "default_extra"), None)),
|
154
|
+
(CustomerNamedTuple, ["Alice", ["O1", "item1", 10.0]], CustomerNamedTuple("Alice", OrderNamedTuple("O1", "item1", 10.0, "default_extra"), None)),
|
120
155
|
]
|
121
156
|
)
|
122
157
|
def test_struct_decoder_cases(data_type, engine_val, expected):
|
@@ -124,17 +159,27 @@ def test_struct_decoder_cases(data_type, engine_val, expected):
|
|
124
159
|
assert decoder(engine_val) == expected
|
125
160
|
|
126
161
|
def test_make_engine_value_decoder_collections():
|
127
|
-
# List of structs
|
162
|
+
# List of structs (dataclass)
|
128
163
|
decoder = build_engine_value_decoder(list[Order])
|
129
164
|
engine_val = [
|
130
165
|
["O1", "item1", 10.0, "default_extra"],
|
131
166
|
["O2", "item2", 20.0, "default_extra"]
|
132
167
|
]
|
133
168
|
assert decoder(engine_val) == [Order("O1", "item1", 10.0, "default_extra"), Order("O2", "item2", 20.0, "default_extra")]
|
169
|
+
|
170
|
+
# List of structs (NamedTuple)
|
171
|
+
decoder = build_engine_value_decoder(list[OrderNamedTuple])
|
172
|
+
assert decoder(engine_val) == [OrderNamedTuple("O1", "item1", 10.0, "default_extra"), OrderNamedTuple("O2", "item2", 20.0, "default_extra")]
|
173
|
+
|
134
174
|
# Struct with list field
|
135
175
|
decoder = build_engine_value_decoder(Customer)
|
136
176
|
engine_val = ["Alice", ["O1", "item1", 10.0, "default_extra"], [["vip"], ["premium"]]]
|
137
177
|
assert decoder(engine_val) == Customer("Alice", Order("O1", "item1", 10.0, "default_extra"), [Tag("vip"), Tag("premium")])
|
178
|
+
|
179
|
+
# NamedTuple with list field
|
180
|
+
decoder = build_engine_value_decoder(CustomerNamedTuple)
|
181
|
+
assert decoder(engine_val) == CustomerNamedTuple("Alice", OrderNamedTuple("O1", "item1", 10.0, "default_extra"), [Tag("vip"), Tag("premium")])
|
182
|
+
|
138
183
|
# Struct with struct field
|
139
184
|
decoder = build_engine_value_decoder(NestedStruct)
|
140
185
|
engine_val = [
|
@@ -239,6 +284,13 @@ def test_roundtrip_ltable():
|
|
239
284
|
assert encoded == [["O1", "item1", 10.0, "default_extra"], ["O2", "item2", 20.0, "default_extra"]]
|
240
285
|
decoded = build_engine_value_decoder(t)(encoded)
|
241
286
|
assert decoded == value
|
287
|
+
|
288
|
+
t_nt = list[OrderNamedTuple]
|
289
|
+
value_nt = [OrderNamedTuple("O1", "item1", 10.0), OrderNamedTuple("O2", "item2", 20.0)]
|
290
|
+
encoded = encode_engine_value(value_nt)
|
291
|
+
assert encoded == [["O1", "item1", 10.0, "default_extra"], ["O2", "item2", 20.0, "default_extra"]]
|
292
|
+
decoded = build_engine_value_decoder(t_nt)(encoded)
|
293
|
+
assert decoded == value_nt
|
242
294
|
|
243
295
|
def test_roundtrip_ktable_str_key():
|
244
296
|
t = dict[str, Order]
|
@@ -247,6 +299,13 @@ def test_roundtrip_ktable_str_key():
|
|
247
299
|
assert encoded == [["K1", "O1", "item1", 10.0, "default_extra"], ["K2", "O2", "item2", 20.0, "default_extra"]]
|
248
300
|
decoded = build_engine_value_decoder(t)(encoded)
|
249
301
|
assert decoded == value
|
302
|
+
|
303
|
+
t_nt = dict[str, OrderNamedTuple]
|
304
|
+
value_nt = {"K1": OrderNamedTuple("O1", "item1", 10.0), "K2": OrderNamedTuple("O2", "item2", 20.0)}
|
305
|
+
encoded = encode_engine_value(value_nt)
|
306
|
+
assert encoded == [["K1", "O1", "item1", 10.0, "default_extra"], ["K2", "O2", "item2", 20.0, "default_extra"]]
|
307
|
+
decoded = build_engine_value_decoder(t_nt)(encoded)
|
308
|
+
assert decoded == value_nt
|
250
309
|
|
251
310
|
def test_roundtrip_ktable_struct_key():
|
252
311
|
@dataclass(frozen=True)
|
@@ -261,6 +320,14 @@ def test_roundtrip_ktable_struct_key():
|
|
261
320
|
[["B", 4], "O2", "item2", 20.0, "default_extra"]]
|
262
321
|
decoded = build_engine_value_decoder(t)(encoded)
|
263
322
|
assert decoded == value
|
323
|
+
|
324
|
+
t_nt = dict[OrderKey, OrderNamedTuple]
|
325
|
+
value_nt = {OrderKey("A", 3): OrderNamedTuple("O1", "item1", 10.0), OrderKey("B", 4): OrderNamedTuple("O2", "item2", 20.0)}
|
326
|
+
encoded = encode_engine_value(value_nt)
|
327
|
+
assert encoded == [[["A", 3], "O1", "item1", 10.0, "default_extra"],
|
328
|
+
[["B", 4], "O2", "item2", 20.0, "default_extra"]]
|
329
|
+
decoded = build_engine_value_decoder(t_nt)(encoded)
|
330
|
+
assert decoded == value_nt
|
264
331
|
|
265
332
|
IntVectorType = cocoindex.Vector[int, Literal[5]]
|
266
333
|
def test_vector_as_vector() -> None:
|
cocoindex/typing.py
CHANGED
@@ -56,8 +56,11 @@ KEY_FIELD_NAME = '_key'
|
|
56
56
|
|
57
57
|
ElementType = type | tuple[type, type]
|
58
58
|
|
59
|
+
def is_namedtuple_type(t) -> bool:
|
60
|
+
return isinstance(t, type) and issubclass(t, tuple) and hasattr(t, "_fields")
|
61
|
+
|
59
62
|
def _is_struct_type(t) -> bool:
|
60
|
-
return isinstance(t, type) and dataclasses.is_dataclass(t)
|
63
|
+
return isinstance(t, type) and (dataclasses.is_dataclass(t) or is_namedtuple_type(t))
|
61
64
|
|
62
65
|
@dataclasses.dataclass
|
63
66
|
class AnalyzedTypeInfo:
|
@@ -69,7 +72,7 @@ class AnalyzedTypeInfo:
|
|
69
72
|
elem_type: ElementType | None # For Vector and Table
|
70
73
|
|
71
74
|
key_type: type | None # For element of KTable
|
72
|
-
|
75
|
+
struct_type: type | None # For Struct, a dataclass or namedtuple
|
73
76
|
|
74
77
|
attrs: dict[str, Any] | None
|
75
78
|
nullable: bool = False
|
@@ -117,15 +120,16 @@ def analyze_type_info(t) -> AnalyzedTypeInfo:
|
|
117
120
|
elif isinstance(attr, TypeKind):
|
118
121
|
kind = attr.kind
|
119
122
|
|
120
|
-
|
123
|
+
struct_type = None
|
121
124
|
elem_type = None
|
122
125
|
key_type = None
|
123
126
|
if _is_struct_type(t):
|
127
|
+
struct_type = t
|
128
|
+
|
124
129
|
if kind is None:
|
125
130
|
kind = 'Struct'
|
126
131
|
elif kind != 'Struct':
|
127
132
|
raise ValueError(f"Unexpected type kind for struct: {kind}")
|
128
|
-
dataclass_type = t
|
129
133
|
elif base_type is collections.abc.Sequence or base_type is list:
|
130
134
|
args = typing.get_args(t)
|
131
135
|
elem_type = args[0]
|
@@ -167,36 +171,50 @@ def analyze_type_info(t) -> AnalyzedTypeInfo:
|
|
167
171
|
else:
|
168
172
|
raise ValueError(f"type unsupported yet: {t}")
|
169
173
|
|
170
|
-
return AnalyzedTypeInfo(
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
174
|
+
return AnalyzedTypeInfo(
|
175
|
+
kind=kind,
|
176
|
+
vector_info=vector_info,
|
177
|
+
elem_type=elem_type,
|
178
|
+
key_type=key_type,
|
179
|
+
struct_type=struct_type,
|
180
|
+
attrs=attrs,
|
181
|
+
nullable=nullable,
|
182
|
+
)
|
183
|
+
|
184
|
+
def _encode_fields_schema(struct_type: type, key_type: type | None = None) -> list[dict[str, Any]]:
|
175
185
|
result = []
|
176
186
|
def add_field(name: str, t) -> None:
|
177
187
|
try:
|
178
188
|
type_info = encode_enriched_type_info(analyze_type_info(t))
|
179
189
|
except ValueError as e:
|
180
|
-
e.add_note(
|
181
|
-
|
190
|
+
e.add_note(
|
191
|
+
f"Failed to encode annotation for field - "
|
192
|
+
f"{struct_type.__name__}.{name}: {t}"
|
193
|
+
)
|
182
194
|
raise
|
183
195
|
type_info['name'] = name
|
184
196
|
result.append(type_info)
|
185
197
|
|
186
198
|
if key_type is not None:
|
187
199
|
add_field(KEY_FIELD_NAME, key_type)
|
188
|
-
|
189
|
-
|
200
|
+
|
201
|
+
if dataclasses.is_dataclass(struct_type):
|
202
|
+
for field in dataclasses.fields(struct_type):
|
203
|
+
add_field(field.name, field.type)
|
204
|
+
elif is_namedtuple_type(struct_type):
|
205
|
+
for name, field_type in struct_type.__annotations__.items():
|
206
|
+
add_field(name, field_type)
|
207
|
+
|
190
208
|
return result
|
191
209
|
|
192
210
|
def _encode_type(type_info: AnalyzedTypeInfo) -> dict[str, Any]:
|
193
211
|
encoded_type: dict[str, Any] = { 'kind': type_info.kind }
|
194
212
|
|
195
213
|
if type_info.kind == 'Struct':
|
196
|
-
if type_info.
|
197
|
-
raise ValueError("Struct type must have a dataclass type")
|
198
|
-
encoded_type['fields'] = _encode_fields_schema(type_info.
|
199
|
-
if doc := inspect.getdoc(type_info.
|
214
|
+
if type_info.struct_type is None:
|
215
|
+
raise ValueError("Struct type must have a dataclass or namedtuple type")
|
216
|
+
encoded_type['fields'] = _encode_fields_schema(type_info.struct_type, type_info.key_type)
|
217
|
+
if doc := inspect.getdoc(type_info.struct_type):
|
200
218
|
encoded_type['description'] = doc
|
201
219
|
|
202
220
|
elif type_info.kind == 'Vector':
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: cocoindex
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.37
|
4
4
|
Requires-Dist: sentence-transformers>=3.3.1
|
5
5
|
Requires-Dist: click>=8.1.8
|
6
6
|
Requires-Dist: rich>=14.0.0
|
@@ -153,7 +153,7 @@ It defines an index flow like this:
|
|
153
153
|
| [Embeddings to Qdrant](examples/text_embedding_qdrant) | Index documents in a Qdrant collection for semantic search |
|
154
154
|
| [FastAPI Server with Docker](examples/fastapi_server_docker) | Run the semantic search server in a Dockerized FastAPI setup |
|
155
155
|
| [Product_Taxonomy_Knowledge_Graph](examples/product_taxonomy_knowledge_graph) | Build knowledge graph for product recommendations |
|
156
|
-
| [Image Search with Vision API](examples/image_search_example) | Generates detailed captions for images using a vision model, embeds them, enables semantic search via FastAPI and served on a React frontend
|
156
|
+
| [Image Search with Vision API](examples/image_search_example) | Generates detailed captions for images using a vision model, embeds them, enables live-updating semantic search via FastAPI and served on a React frontend|
|
157
157
|
|
158
158
|
More coming and stay tuned 👀!
|
159
159
|
|
@@ -1,11 +1,11 @@
|
|
1
|
-
cocoindex-0.1.
|
2
|
-
cocoindex-0.1.
|
3
|
-
cocoindex-0.1.
|
1
|
+
cocoindex-0.1.37.dist-info/METADATA,sha256=q_5xT2W7gjD_IiCl_pBr__gMfn3ldcoFppGNTmROkMY,9699
|
2
|
+
cocoindex-0.1.37.dist-info/WHEEL,sha256=ryp1uXCltaq3TPfjxhSWoam4EHMC40-Yabw-yvEW9gU,104
|
3
|
+
cocoindex-0.1.37.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
4
4
|
cocoindex/__init__.py,sha256=LpB0VjGvkD1beio8R9RCT6PI3eU0keV-3sBL45fHTQE,690
|
5
|
-
cocoindex/_engine.cpython-312-darwin.so,sha256=
|
5
|
+
cocoindex/_engine.cpython-312-darwin.so,sha256=ixvMKTQ1mhgoOpeiCWhmtTuxFhpjTKD0D1mrpP6FeoI,56739584
|
6
6
|
cocoindex/auth_registry.py,sha256=NsALZ3SKsDG9cPdrlTlalIqUvgbgFOaFGAbWJNedtJE,692
|
7
|
-
cocoindex/cli.py,sha256=
|
8
|
-
cocoindex/convert.py,sha256=
|
7
|
+
cocoindex/cli.py,sha256=1PJTdwX-X9r6Obw_W7VZDUgd5E7Xw6tLyvSDT8dFnY8,8924
|
8
|
+
cocoindex/convert.py,sha256=tRY-QBeeFMFwCYiRk7a0_tuDqopw8iqBpg_Aswcq9JQ,6864
|
9
9
|
cocoindex/flow.py,sha256=MZZ0Uf0ObAzR1yIjUecRgA-U0t__95eoLBK_DxwwLnk,23375
|
10
10
|
cocoindex/functions.py,sha256=F79dNmGE127LaU67kF5Oqtf_tIzebFQH7MkyceMX4-s,1830
|
11
11
|
cocoindex/index.py,sha256=LssEOuZi6AqhwKtZM3QFeQpa9T-0ELi8G5DsrYKECvc,534
|
@@ -17,9 +17,9 @@ cocoindex/query.py,sha256=8_3Lb_EVjZtl2ZyJNZGX16LoKXEd-PL8OjY-zs9GQeA,3205
|
|
17
17
|
cocoindex/runtime.py,sha256=jqRnWkkIlAhE04gi4y0Y5bzuq9FX4j0aVNU-nengLJk,980
|
18
18
|
cocoindex/setting.py,sha256=pms1blwlXIOqZIpye-rfiwzqYUCAC8oEL7mQM5A160g,2356
|
19
19
|
cocoindex/setup.py,sha256=AQLbtBLuJX066IANS7BGp20246mAGQ_4Z0W6MVJcQzY,481
|
20
|
-
cocoindex/sources.py,sha256=
|
20
|
+
cocoindex/sources.py,sha256=7lpwYLsFCRfbURKf79Vu0JZZoXjAYY0DxNHzUb-VHBY,1327
|
21
21
|
cocoindex/storages.py,sha256=MFMsfyOCYMggTWeWrOi82miqOXQmiUuqq828x5htBr0,2207
|
22
22
|
cocoindex/tests/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
23
|
-
cocoindex/tests/test_convert.py,sha256=
|
24
|
-
cocoindex/typing.py,sha256=
|
25
|
-
cocoindex-0.1.
|
23
|
+
cocoindex/tests/test_convert.py,sha256=7jc--I3frrg7DB5MPr4JFzE7DSCznJuWyHdlDLQJ_fM,15516
|
24
|
+
cocoindex/typing.py,sha256=369ABRtnpbaVSQVIBc2ZDutXW8jUmncvNJd9CHEWT3Q,8962
|
25
|
+
cocoindex-0.1.37.dist-info/RECORD,,
|
File without changes
|