cocoindex 0.1.73__cp313-cp313-macosx_11_0_arm64.whl → 0.1.74__cp313-cp313-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-313-darwin.so +0 -0
- cocoindex/convert.py +149 -97
- cocoindex/flow.py +2 -1
- cocoindex/op.py +263 -6
- cocoindex/tests/test_convert.py +73 -127
- cocoindex/tests/test_typing.py +5 -12
- cocoindex/typing.py +4 -4
- {cocoindex-0.1.73.dist-info → cocoindex-0.1.74.dist-info}/METADATA +2 -2
- {cocoindex-0.1.73.dist-info → cocoindex-0.1.74.dist-info}/RECORD +12 -12
- {cocoindex-0.1.73.dist-info → cocoindex-0.1.74.dist-info}/WHEEL +0 -0
- {cocoindex-0.1.73.dist-info → cocoindex-0.1.74.dist-info}/entry_points.txt +0 -0
- {cocoindex-0.1.73.dist-info → cocoindex-0.1.74.dist-info}/licenses/LICENSE +0 -0
Binary file
|
cocoindex/convert.py
CHANGED
@@ -2,9 +2,12 @@
|
|
2
2
|
Utilities to convert between Python and engine values.
|
3
3
|
"""
|
4
4
|
|
5
|
+
from __future__ import annotations
|
6
|
+
|
5
7
|
import dataclasses
|
6
8
|
import datetime
|
7
9
|
import inspect
|
10
|
+
import warnings
|
8
11
|
from enum import Enum
|
9
12
|
from typing import Any, Callable, Mapping, get_origin
|
10
13
|
|
@@ -29,6 +32,24 @@ from .typing import (
|
|
29
32
|
)
|
30
33
|
|
31
34
|
|
35
|
+
class ChildFieldPath:
|
36
|
+
"""Context manager to append a field to field_path on enter and pop it on exit."""
|
37
|
+
|
38
|
+
_field_path: list[str]
|
39
|
+
_field_name: str
|
40
|
+
|
41
|
+
def __init__(self, field_path: list[str], field_name: str):
|
42
|
+
self._field_path: list[str] = field_path
|
43
|
+
self._field_name = field_name
|
44
|
+
|
45
|
+
def __enter__(self) -> ChildFieldPath:
|
46
|
+
self._field_path.append(self._field_name)
|
47
|
+
return self
|
48
|
+
|
49
|
+
def __exit__(self, _exc_type: Any, _exc_val: Any, _exc_tb: Any) -> None:
|
50
|
+
self._field_path.pop()
|
51
|
+
|
52
|
+
|
32
53
|
def encode_engine_value(value: Any) -> Any:
|
33
54
|
"""Encode a Python value to an engine value."""
|
34
55
|
if dataclasses.is_dataclass(value):
|
@@ -73,7 +94,7 @@ def _is_type_kind_convertible_to(src_type_kind: str, dst_type_kind: str) -> bool
|
|
73
94
|
def make_engine_value_decoder(
|
74
95
|
field_path: list[str],
|
75
96
|
src_type: dict[str, Any],
|
76
|
-
|
97
|
+
dst_type_info: AnalyzedTypeInfo,
|
77
98
|
) -> Callable[[Any], Any]:
|
78
99
|
"""
|
79
100
|
Make a decoder from an engine value to a Python value.
|
@@ -89,7 +110,6 @@ def make_engine_value_decoder(
|
|
89
110
|
|
90
111
|
src_type_kind = src_type["kind"]
|
91
112
|
|
92
|
-
dst_type_info = analyze_type_info(dst_annotation)
|
93
113
|
dst_type_variant = dst_type_info.variant
|
94
114
|
|
95
115
|
if isinstance(dst_type_variant, AnalyzedUnknownType):
|
@@ -99,96 +119,97 @@ def make_engine_value_decoder(
|
|
99
119
|
)
|
100
120
|
|
101
121
|
if src_type_kind == "Struct":
|
102
|
-
return
|
122
|
+
return make_engine_struct_decoder(
|
103
123
|
field_path,
|
104
124
|
src_type["fields"],
|
105
125
|
dst_type_info,
|
106
126
|
)
|
107
127
|
|
108
128
|
if src_type_kind in TABLE_TYPES:
|
109
|
-
field_path
|
110
|
-
|
129
|
+
with ChildFieldPath(field_path, "[*]"):
|
130
|
+
engine_fields_schema = src_type["row"]["fields"]
|
111
131
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
132
|
+
if src_type_kind == "LTable":
|
133
|
+
if isinstance(dst_type_variant, AnalyzedAnyType):
|
134
|
+
return _make_engine_ltable_to_list_dict_decoder(
|
135
|
+
field_path, engine_fields_schema
|
136
|
+
)
|
137
|
+
if not isinstance(dst_type_variant, AnalyzedListType):
|
138
|
+
raise ValueError(
|
139
|
+
f"Type mismatch for `{''.join(field_path)}`: "
|
140
|
+
f"declared `{dst_type_info.core_type}`, a list type expected"
|
141
|
+
)
|
142
|
+
row_decoder = make_engine_struct_decoder(
|
143
|
+
field_path,
|
144
|
+
engine_fields_schema,
|
145
|
+
analyze_type_info(dst_type_variant.elem_type),
|
121
146
|
)
|
122
|
-
row_decoder = _make_engine_struct_value_decoder(
|
123
|
-
field_path,
|
124
|
-
engine_fields_schema,
|
125
|
-
analyze_type_info(dst_type_variant.elem_type),
|
126
|
-
)
|
127
147
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
148
|
+
def decode(value: Any) -> Any | None:
|
149
|
+
if value is None:
|
150
|
+
return None
|
151
|
+
return [row_decoder(v) for v in value]
|
132
152
|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
153
|
+
elif src_type_kind == "KTable":
|
154
|
+
if isinstance(dst_type_variant, AnalyzedAnyType):
|
155
|
+
return _make_engine_ktable_to_dict_dict_decoder(
|
156
|
+
field_path, engine_fields_schema
|
157
|
+
)
|
158
|
+
if not isinstance(dst_type_variant, AnalyzedDictType):
|
159
|
+
raise ValueError(
|
160
|
+
f"Type mismatch for `{''.join(field_path)}`: "
|
161
|
+
f"declared `{dst_type_info.core_type}`, a dict type expected"
|
162
|
+
)
|
163
|
+
|
164
|
+
key_field_schema = engine_fields_schema[0]
|
165
|
+
field_path.append(f".{key_field_schema.get('name', KEY_FIELD_NAME)}")
|
166
|
+
key_decoder = make_engine_value_decoder(
|
167
|
+
field_path,
|
168
|
+
key_field_schema["type"],
|
169
|
+
analyze_type_info(dst_type_variant.key_type),
|
137
170
|
)
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
171
|
+
field_path.pop()
|
172
|
+
value_decoder = make_engine_struct_decoder(
|
173
|
+
field_path,
|
174
|
+
engine_fields_schema[1:],
|
175
|
+
analyze_type_info(dst_type_variant.value_type),
|
142
176
|
)
|
143
177
|
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
)
|
149
|
-
field_path.pop()
|
150
|
-
value_decoder = _make_engine_struct_value_decoder(
|
151
|
-
field_path,
|
152
|
-
engine_fields_schema[1:],
|
153
|
-
analyze_type_info(dst_type_variant.value_type),
|
154
|
-
)
|
155
|
-
|
156
|
-
def decode(value: Any) -> Any | None:
|
157
|
-
if value is None:
|
158
|
-
return None
|
159
|
-
return {key_decoder(v[0]): value_decoder(v[1:]) for v in value}
|
178
|
+
def decode(value: Any) -> Any | None:
|
179
|
+
if value is None:
|
180
|
+
return None
|
181
|
+
return {key_decoder(v[0]): value_decoder(v[1:]) for v in value}
|
160
182
|
|
161
|
-
field_path.pop()
|
162
183
|
return decode
|
163
184
|
|
164
185
|
if src_type_kind == "Union":
|
165
186
|
if isinstance(dst_type_variant, AnalyzedAnyType):
|
166
187
|
return lambda value: value[1]
|
167
188
|
|
168
|
-
|
169
|
-
dst_type_variant.variant_types
|
189
|
+
dst_type_info_variants = (
|
190
|
+
[analyze_type_info(t) for t in dst_type_variant.variant_types]
|
170
191
|
if isinstance(dst_type_variant, AnalyzedUnionType)
|
171
|
-
else [
|
192
|
+
else [dst_type_info]
|
172
193
|
)
|
173
194
|
src_type_variants = src_type["types"]
|
174
195
|
decoders = []
|
175
196
|
for i, src_type_variant in enumerate(src_type_variants):
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
197
|
+
with ChildFieldPath(field_path, f"[{i}]"):
|
198
|
+
decoder = None
|
199
|
+
for dst_type_info_variant in dst_type_info_variants:
|
200
|
+
try:
|
201
|
+
decoder = make_engine_value_decoder(
|
202
|
+
field_path, src_type_variant, dst_type_info_variant
|
203
|
+
)
|
204
|
+
break
|
205
|
+
except ValueError:
|
206
|
+
pass
|
207
|
+
if decoder is None:
|
208
|
+
raise ValueError(
|
209
|
+
f"Type mismatch for `{''.join(field_path)}`: "
|
210
|
+
f"cannot find matched target type for source type variant {src_type_variant}"
|
182
211
|
)
|
183
|
-
|
184
|
-
except ValueError:
|
185
|
-
pass
|
186
|
-
if decoder is None:
|
187
|
-
raise ValueError(
|
188
|
-
f"Type mismatch for `{''.join(field_path)}`: "
|
189
|
-
f"cannot find matched target type for source type variant {src_type_variant}"
|
190
|
-
)
|
191
|
-
decoders.append(decoder)
|
212
|
+
decoders.append(decoder)
|
192
213
|
return lambda value: decoders[value[0]](value[1])
|
193
214
|
|
194
215
|
if isinstance(dst_type_variant, AnalyzedAnyType):
|
@@ -216,7 +237,7 @@ def make_engine_value_decoder(
|
|
216
237
|
vec_elem_decoder = make_engine_value_decoder(
|
217
238
|
field_path + ["[*]"],
|
218
239
|
src_type["element_type"],
|
219
|
-
dst_type_variant and dst_type_variant.elem_type,
|
240
|
+
analyze_type_info(dst_type_variant and dst_type_variant.elem_type),
|
220
241
|
)
|
221
242
|
|
222
243
|
def decode_vector(value: Any) -> Any | None:
|
@@ -247,7 +268,7 @@ def make_engine_value_decoder(
|
|
247
268
|
if not _is_type_kind_convertible_to(src_type_kind, dst_type_variant.kind):
|
248
269
|
raise ValueError(
|
249
270
|
f"Type mismatch for `{''.join(field_path)}`: "
|
250
|
-
f"passed in {src_type_kind}, declared {
|
271
|
+
f"passed in {src_type_kind}, declared {dst_type_info.core_type} ({dst_type_variant.kind})"
|
251
272
|
)
|
252
273
|
|
253
274
|
if dst_type_variant.kind in ("Float32", "Float64", "Int64"):
|
@@ -267,7 +288,31 @@ def make_engine_value_decoder(
|
|
267
288
|
return lambda value: value
|
268
289
|
|
269
290
|
|
270
|
-
def
|
291
|
+
def _get_auto_default_for_type(
|
292
|
+
type_info: AnalyzedTypeInfo,
|
293
|
+
) -> tuple[Any, bool]:
|
294
|
+
"""
|
295
|
+
Get an auto-default value for a type annotation if it's safe to do so.
|
296
|
+
|
297
|
+
Returns:
|
298
|
+
A tuple of (default_value, is_supported) where:
|
299
|
+
- default_value: The default value if auto-defaulting is supported
|
300
|
+
- is_supported: True if auto-defaulting is supported for this type
|
301
|
+
"""
|
302
|
+
# Case 1: Nullable types (Optional[T] or T | None)
|
303
|
+
if type_info.nullable:
|
304
|
+
return None, True
|
305
|
+
|
306
|
+
# Case 2: Table types (KTable or LTable) - check if it's a list or dict type
|
307
|
+
if isinstance(type_info.variant, AnalyzedListType):
|
308
|
+
return [], True
|
309
|
+
elif isinstance(type_info.variant, AnalyzedDictType):
|
310
|
+
return {}, True
|
311
|
+
|
312
|
+
return None, False
|
313
|
+
|
314
|
+
|
315
|
+
def make_engine_struct_decoder(
|
271
316
|
field_path: list[str],
|
272
317
|
src_fields: list[dict[str, Any]],
|
273
318
|
dst_type_info: AnalyzedTypeInfo,
|
@@ -321,32 +366,39 @@ def _make_engine_struct_value_decoder(
|
|
321
366
|
else:
|
322
367
|
raise ValueError(f"Unsupported struct type: {dst_struct_type}")
|
323
368
|
|
324
|
-
def
|
369
|
+
def make_closure_for_field(
|
325
370
|
name: str, param: inspect.Parameter
|
326
371
|
) -> Callable[[list[Any]], Any]:
|
327
372
|
src_idx = src_name_to_idx.get(name)
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
|
334
|
-
|
335
|
-
lambda values: field_decoder(values[src_idx])
|
336
|
-
|
337
|
-
|
338
|
-
|
373
|
+
type_info = analyze_type_info(param.annotation)
|
374
|
+
|
375
|
+
with ChildFieldPath(field_path, f".{name}"):
|
376
|
+
if src_idx is not None:
|
377
|
+
field_decoder = make_engine_value_decoder(
|
378
|
+
field_path, src_fields[src_idx]["type"], type_info
|
379
|
+
)
|
380
|
+
return lambda values: field_decoder(values[src_idx])
|
381
|
+
|
382
|
+
default_value = param.default
|
383
|
+
if default_value is not inspect.Parameter.empty:
|
384
|
+
return lambda _: default_value
|
385
|
+
|
386
|
+
auto_default, is_supported = _get_auto_default_for_type(type_info)
|
387
|
+
if is_supported:
|
388
|
+
warnings.warn(
|
389
|
+
f"Field '{name}' (type {param.annotation}) without default value is missing in input: "
|
390
|
+
f"{''.join(field_path)}. Auto-assigning default value: {auto_default}",
|
391
|
+
UserWarning,
|
392
|
+
stacklevel=4,
|
393
|
+
)
|
394
|
+
return lambda _: auto_default
|
339
395
|
|
340
|
-
default_value = param.default
|
341
|
-
if default_value is inspect.Parameter.empty:
|
342
396
|
raise ValueError(
|
343
|
-
f"Field without default value is missing in input: {''.join(field_path)}"
|
397
|
+
f"Field '{name}' (type {param.annotation}) without default value is missing in input: {''.join(field_path)}"
|
344
398
|
)
|
345
399
|
|
346
|
-
return lambda _: default_value
|
347
|
-
|
348
400
|
field_value_decoder = [
|
349
|
-
|
401
|
+
make_closure_for_field(name, param) for (name, param) in parameters.items()
|
350
402
|
]
|
351
403
|
|
352
404
|
return lambda values: dst_struct_type(
|
@@ -363,13 +415,12 @@ def _make_engine_struct_to_dict_decoder(
|
|
363
415
|
field_decoders = []
|
364
416
|
for i, field_schema in enumerate(src_fields):
|
365
417
|
field_name = field_schema["name"]
|
366
|
-
field_path
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
field_path.pop()
|
418
|
+
with ChildFieldPath(field_path, f".{field_name}"):
|
419
|
+
field_decoder = make_engine_value_decoder(
|
420
|
+
field_path,
|
421
|
+
field_schema["type"],
|
422
|
+
analyze_type_info(Any), # Use Any for recursive decoding
|
423
|
+
)
|
373
424
|
field_decoders.append((field_name, field_decoder))
|
374
425
|
|
375
426
|
def decode_to_dict(values: list[Any] | None) -> dict[str, Any] | None:
|
@@ -426,9 +477,10 @@ def _make_engine_ktable_to_dict_dict_decoder(
|
|
426
477
|
value_fields_schema = src_fields[1:]
|
427
478
|
|
428
479
|
# Create decoders
|
429
|
-
field_path
|
430
|
-
|
431
|
-
|
480
|
+
with ChildFieldPath(field_path, f".{key_field_schema.get('name', KEY_FIELD_NAME)}"):
|
481
|
+
key_decoder = make_engine_value_decoder(
|
482
|
+
field_path, key_field_schema["type"], analyze_type_info(Any)
|
483
|
+
)
|
432
484
|
|
433
485
|
value_decoder = _make_engine_struct_to_dict_decoder(field_path, value_fields_schema)
|
434
486
|
|
cocoindex/flow.py
CHANGED
@@ -16,6 +16,7 @@ from .validation import (
|
|
16
16
|
validate_full_flow_name,
|
17
17
|
validate_target_name,
|
18
18
|
)
|
19
|
+
from .typing import analyze_type_info
|
19
20
|
|
20
21
|
from dataclasses import dataclass
|
21
22
|
from enum import Enum
|
@@ -1053,7 +1054,7 @@ class TransformFlow(Generic[T]):
|
|
1053
1054
|
sig.return_annotation
|
1054
1055
|
)
|
1055
1056
|
result_decoder = make_engine_value_decoder(
|
1056
|
-
[], engine_return_type["type"], python_return_type
|
1057
|
+
[], engine_return_type["type"], analyze_type_info(python_return_type)
|
1057
1058
|
)
|
1058
1059
|
|
1059
1060
|
return TransformFlowInfo(engine_flow, result_decoder)
|
cocoindex/op.py
CHANGED
@@ -6,11 +6,31 @@ import asyncio
|
|
6
6
|
import dataclasses
|
7
7
|
import inspect
|
8
8
|
from enum import Enum
|
9
|
-
from typing import
|
9
|
+
from typing import (
|
10
|
+
Any,
|
11
|
+
Awaitable,
|
12
|
+
Callable,
|
13
|
+
Protocol,
|
14
|
+
dataclass_transform,
|
15
|
+
Annotated,
|
16
|
+
get_args,
|
17
|
+
)
|
10
18
|
|
11
19
|
from . import _engine # type: ignore
|
12
|
-
from .convert import
|
13
|
-
|
20
|
+
from .convert import (
|
21
|
+
encode_engine_value,
|
22
|
+
make_engine_value_decoder,
|
23
|
+
make_engine_struct_decoder,
|
24
|
+
)
|
25
|
+
from .typing import (
|
26
|
+
TypeAttr,
|
27
|
+
encode_enriched_type,
|
28
|
+
resolve_forward_ref,
|
29
|
+
analyze_type_info,
|
30
|
+
AnalyzedAnyType,
|
31
|
+
AnalyzedBasicType,
|
32
|
+
AnalyzedDictType,
|
33
|
+
)
|
14
34
|
|
15
35
|
|
16
36
|
class OpCategory(Enum):
|
@@ -65,6 +85,22 @@ class Executor(Protocol):
|
|
65
85
|
op_category: OpCategory
|
66
86
|
|
67
87
|
|
88
|
+
def _load_spec_from_engine(spec_cls: type, spec: dict[str, Any]) -> Any:
|
89
|
+
"""
|
90
|
+
Load a spec from the engine.
|
91
|
+
"""
|
92
|
+
return spec_cls(**spec)
|
93
|
+
|
94
|
+
|
95
|
+
def _get_required_method(cls: type, name: str) -> Callable[..., Any]:
|
96
|
+
method = getattr(cls, name, None)
|
97
|
+
if method is None:
|
98
|
+
raise ValueError(f"Method {name}() is required for {cls.__name__}")
|
99
|
+
if not inspect.isfunction(method):
|
100
|
+
raise ValueError(f"Method {cls.__name__}.{name}() is not a function")
|
101
|
+
return method
|
102
|
+
|
103
|
+
|
68
104
|
class _FunctionExecutorFactory:
|
69
105
|
_spec_cls: type
|
70
106
|
_executor_cls: type
|
@@ -76,7 +112,7 @@ class _FunctionExecutorFactory:
|
|
76
112
|
def __call__(
|
77
113
|
self, spec: dict[str, Any], *args: Any, **kwargs: Any
|
78
114
|
) -> tuple[dict[str, Any], Executor]:
|
79
|
-
spec = self._spec_cls
|
115
|
+
spec = _load_spec_from_engine(self._spec_cls, spec)
|
80
116
|
executor = self._executor_cls(spec)
|
81
117
|
result_type = executor.analyze(*args, **kwargs)
|
82
118
|
return (encode_enriched_type(result_type), executor)
|
@@ -185,7 +221,9 @@ def _register_op_factory(
|
|
185
221
|
)
|
186
222
|
self._args_decoders.append(
|
187
223
|
make_engine_value_decoder(
|
188
|
-
[arg_name],
|
224
|
+
[arg_name],
|
225
|
+
arg.value_type["type"],
|
226
|
+
analyze_type_info(arg_param.annotation),
|
189
227
|
)
|
190
228
|
)
|
191
229
|
process_attribute(arg_name, arg)
|
@@ -217,7 +255,9 @@ def _register_op_factory(
|
|
217
255
|
)
|
218
256
|
arg_param = expected_arg[1]
|
219
257
|
self._kwargs_decoders[kwarg_name] = make_engine_value_decoder(
|
220
|
-
[kwarg_name],
|
258
|
+
[kwarg_name],
|
259
|
+
kwarg.value_type["type"],
|
260
|
+
analyze_type_info(arg_param.annotation),
|
221
261
|
)
|
222
262
|
process_attribute(kwarg_name, kwarg)
|
223
263
|
|
@@ -359,3 +399,220 @@ def function(**args: Any) -> Callable[[Callable[..., Any]], FunctionSpec]:
|
|
359
399
|
return _Spec()
|
360
400
|
|
361
401
|
return _inner
|
402
|
+
|
403
|
+
|
404
|
+
########################################################
|
405
|
+
# Custom target connector
|
406
|
+
########################################################
|
407
|
+
|
408
|
+
|
409
|
+
@dataclasses.dataclass
|
410
|
+
class _TargetConnectorContext:
|
411
|
+
target_name: str
|
412
|
+
spec: Any
|
413
|
+
prepared_spec: Any
|
414
|
+
key_decoder: Callable[[Any], Any]
|
415
|
+
value_decoder: Callable[[Any], Any]
|
416
|
+
|
417
|
+
|
418
|
+
class _TargetConnector:
|
419
|
+
"""
|
420
|
+
The connector class passed to the engine.
|
421
|
+
"""
|
422
|
+
|
423
|
+
_spec_cls: type
|
424
|
+
_connector_cls: type
|
425
|
+
|
426
|
+
_get_persistent_key_fn: Callable[[_TargetConnectorContext, str], Any]
|
427
|
+
_apply_setup_change_async_fn: Callable[
|
428
|
+
[Any, dict[str, Any] | None, dict[str, Any] | None], Awaitable[None]
|
429
|
+
]
|
430
|
+
_mutate_async_fn: Callable[..., Awaitable[None]]
|
431
|
+
_mutatation_type: AnalyzedDictType | None
|
432
|
+
|
433
|
+
def __init__(self, spec_cls: type, connector_cls: type):
|
434
|
+
self._spec_cls = spec_cls
|
435
|
+
self._connector_cls = connector_cls
|
436
|
+
|
437
|
+
self._get_persistent_key_fn = _get_required_method(
|
438
|
+
connector_cls, "get_persistent_key"
|
439
|
+
)
|
440
|
+
self._apply_setup_change_async_fn = _to_async_call(
|
441
|
+
_get_required_method(connector_cls, "apply_setup_change")
|
442
|
+
)
|
443
|
+
|
444
|
+
mutate_fn = _get_required_method(connector_cls, "mutate")
|
445
|
+
self._mutate_async_fn = _to_async_call(mutate_fn)
|
446
|
+
|
447
|
+
# Store the type annotation for later use
|
448
|
+
self._mutatation_type = self._analyze_mutate_mutation_type(
|
449
|
+
connector_cls, mutate_fn
|
450
|
+
)
|
451
|
+
|
452
|
+
@staticmethod
|
453
|
+
def _analyze_mutate_mutation_type(
|
454
|
+
connector_cls: type, mutate_fn: Callable[..., Any]
|
455
|
+
) -> AnalyzedDictType | None:
|
456
|
+
# Validate mutate_fn signature and extract type annotation
|
457
|
+
mutate_sig = inspect.signature(mutate_fn)
|
458
|
+
params = list(mutate_sig.parameters.values())
|
459
|
+
|
460
|
+
if len(params) != 1:
|
461
|
+
raise ValueError(
|
462
|
+
f"Method {connector_cls.__name__}.mutate(*args) must have exactly one parameter, "
|
463
|
+
f"got {len(params)}"
|
464
|
+
)
|
465
|
+
|
466
|
+
param = params[0]
|
467
|
+
if param.kind != inspect.Parameter.VAR_POSITIONAL:
|
468
|
+
raise ValueError(
|
469
|
+
f"Method {connector_cls.__name__}.mutate(*args) parameter must be *args format, "
|
470
|
+
f"got {param.kind.name}"
|
471
|
+
)
|
472
|
+
|
473
|
+
# Extract type annotation
|
474
|
+
analyzed_args_type = analyze_type_info(param.annotation)
|
475
|
+
if isinstance(analyzed_args_type.variant, AnalyzedAnyType):
|
476
|
+
return None
|
477
|
+
|
478
|
+
if analyzed_args_type.base_type is tuple:
|
479
|
+
args = get_args(analyzed_args_type.core_type)
|
480
|
+
if not args:
|
481
|
+
return None
|
482
|
+
if len(args) == 2:
|
483
|
+
mutation_type = analyze_type_info(args[1])
|
484
|
+
if isinstance(mutation_type.variant, AnalyzedAnyType):
|
485
|
+
return None
|
486
|
+
if isinstance(mutation_type.variant, AnalyzedDictType):
|
487
|
+
return mutation_type.variant
|
488
|
+
|
489
|
+
raise ValueError(
|
490
|
+
f"Method {connector_cls.__name__}.mutate(*args) parameter must be a tuple with "
|
491
|
+
f"2 elements (tuple[SpecType, dict[str, ValueStruct]], spec and mutation in dict), "
|
492
|
+
"got {args_type}"
|
493
|
+
)
|
494
|
+
|
495
|
+
def create_export_context(
|
496
|
+
self,
|
497
|
+
name: str,
|
498
|
+
spec: dict[str, Any],
|
499
|
+
key_fields_schema: list[Any],
|
500
|
+
value_fields_schema: list[Any],
|
501
|
+
) -> _TargetConnectorContext:
|
502
|
+
key_annotation, value_annotation = (
|
503
|
+
(
|
504
|
+
self._mutatation_type.key_type,
|
505
|
+
self._mutatation_type.value_type,
|
506
|
+
)
|
507
|
+
if self._mutatation_type is not None
|
508
|
+
else (None, None)
|
509
|
+
)
|
510
|
+
|
511
|
+
key_type_info = analyze_type_info(key_annotation)
|
512
|
+
if (
|
513
|
+
len(key_fields_schema) == 1
|
514
|
+
and key_fields_schema[0]["type"]["kind"] != "Struct"
|
515
|
+
and isinstance(key_type_info.variant, (AnalyzedAnyType, AnalyzedBasicType))
|
516
|
+
):
|
517
|
+
# Special case for ease of use: single key column can be mapped to a basic type without the wrapper struct.
|
518
|
+
key_decoder = make_engine_value_decoder(
|
519
|
+
["(key)"],
|
520
|
+
key_fields_schema[0]["type"],
|
521
|
+
key_type_info,
|
522
|
+
)
|
523
|
+
else:
|
524
|
+
key_decoder = make_engine_struct_decoder(
|
525
|
+
["(key)"], key_fields_schema, key_type_info
|
526
|
+
)
|
527
|
+
|
528
|
+
value_decoder = make_engine_struct_decoder(
|
529
|
+
["(value)"], value_fields_schema, analyze_type_info(value_annotation)
|
530
|
+
)
|
531
|
+
|
532
|
+
loaded_spec = _load_spec_from_engine(self._spec_cls, spec)
|
533
|
+
prepare_method = getattr(self._connector_cls, "prepare", None)
|
534
|
+
if prepare_method is None:
|
535
|
+
prepared_spec = loaded_spec
|
536
|
+
else:
|
537
|
+
prepared_spec = prepare_method(loaded_spec)
|
538
|
+
|
539
|
+
return _TargetConnectorContext(
|
540
|
+
target_name=name,
|
541
|
+
spec=loaded_spec,
|
542
|
+
prepared_spec=prepared_spec,
|
543
|
+
key_decoder=key_decoder,
|
544
|
+
value_decoder=value_decoder,
|
545
|
+
)
|
546
|
+
|
547
|
+
def get_persistent_key(self, export_context: _TargetConnectorContext) -> Any:
|
548
|
+
return self._get_persistent_key_fn(
|
549
|
+
export_context.spec, export_context.target_name
|
550
|
+
)
|
551
|
+
|
552
|
+
def describe_resource(self, key: Any) -> str:
|
553
|
+
describe_fn = getattr(self._connector_cls, "describe", None)
|
554
|
+
if describe_fn is None:
|
555
|
+
return str(key)
|
556
|
+
return str(describe_fn(key))
|
557
|
+
|
558
|
+
async def apply_setup_changes_async(
|
559
|
+
self,
|
560
|
+
changes: list[tuple[Any, list[dict[str, Any] | None], dict[str, Any] | None]],
|
561
|
+
) -> None:
|
562
|
+
for key, previous, current in changes:
|
563
|
+
prev_specs = [
|
564
|
+
_load_spec_from_engine(self._spec_cls, spec)
|
565
|
+
if spec is not None
|
566
|
+
else None
|
567
|
+
for spec in previous
|
568
|
+
]
|
569
|
+
curr_spec = (
|
570
|
+
_load_spec_from_engine(self._spec_cls, current)
|
571
|
+
if current is not None
|
572
|
+
else None
|
573
|
+
)
|
574
|
+
for prev_spec in prev_specs:
|
575
|
+
await self._apply_setup_change_async_fn(key, prev_spec, curr_spec)
|
576
|
+
|
577
|
+
@staticmethod
|
578
|
+
def _decode_mutation(
|
579
|
+
context: _TargetConnectorContext, mutation: list[tuple[Any, Any | None]]
|
580
|
+
) -> tuple[Any, dict[Any, Any | None]]:
|
581
|
+
return (
|
582
|
+
context.prepared_spec,
|
583
|
+
{
|
584
|
+
context.key_decoder(key): (
|
585
|
+
context.value_decoder(value) if value is not None else None
|
586
|
+
)
|
587
|
+
for key, value in mutation
|
588
|
+
},
|
589
|
+
)
|
590
|
+
|
591
|
+
async def mutate_async(
|
592
|
+
self,
|
593
|
+
mutations: list[tuple[_TargetConnectorContext, list[tuple[Any, Any | None]]]],
|
594
|
+
) -> None:
|
595
|
+
await self._mutate_async_fn(
|
596
|
+
*(
|
597
|
+
self._decode_mutation(context, mutation)
|
598
|
+
for context, mutation in mutations
|
599
|
+
)
|
600
|
+
)
|
601
|
+
|
602
|
+
|
603
|
+
def target_connector(spec_cls: type) -> Callable[[type], type]:
|
604
|
+
"""
|
605
|
+
Decorate a class to provide a target connector for an op.
|
606
|
+
"""
|
607
|
+
|
608
|
+
# Validate the spec_cls is a TargetSpec.
|
609
|
+
if not issubclass(spec_cls, TargetSpec):
|
610
|
+
raise ValueError(f"Expect a TargetSpec, got {spec_cls}")
|
611
|
+
|
612
|
+
# Register the target connector.
|
613
|
+
def _inner(connector_cls: type) -> type:
|
614
|
+
connector = _TargetConnector(spec_cls, connector_cls)
|
615
|
+
_engine.register_target_connector(spec_cls.__name__, connector)
|
616
|
+
return connector_cls
|
617
|
+
|
618
|
+
return _inner
|
cocoindex/tests/test_convert.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
import datetime
|
2
2
|
import uuid
|
3
|
-
from dataclasses import dataclass, make_dataclass
|
3
|
+
from dataclasses import dataclass, make_dataclass, field
|
4
4
|
from typing import Annotated, Any, Callable, Literal, NamedTuple
|
5
5
|
|
6
6
|
import numpy as np
|
@@ -19,6 +19,7 @@ from cocoindex.typing import (
|
|
19
19
|
TypeKind,
|
20
20
|
Vector,
|
21
21
|
encode_enriched_type,
|
22
|
+
analyze_type_info,
|
22
23
|
)
|
23
24
|
|
24
25
|
|
@@ -75,7 +76,9 @@ def build_engine_value_decoder(
|
|
75
76
|
If python_type is not specified, uses engine_type_in_py as the target.
|
76
77
|
"""
|
77
78
|
engine_type = encode_enriched_type(engine_type_in_py)["type"]
|
78
|
-
return make_engine_value_decoder(
|
79
|
+
return make_engine_value_decoder(
|
80
|
+
[], engine_type, analyze_type_info(python_type or engine_type_in_py)
|
81
|
+
)
|
79
82
|
|
80
83
|
|
81
84
|
def validate_full_roundtrip_to(
|
@@ -103,7 +106,9 @@ def validate_full_roundtrip_to(
|
|
103
106
|
)
|
104
107
|
|
105
108
|
for other_value, other_type in decoded_values:
|
106
|
-
decoder = make_engine_value_decoder(
|
109
|
+
decoder = make_engine_value_decoder(
|
110
|
+
[], encoded_output_type, analyze_type_info(other_type)
|
111
|
+
)
|
107
112
|
other_decoded_value = decoder(value_from_engine)
|
108
113
|
assert eq(other_decoded_value, other_value), (
|
109
114
|
f"Expected {other_value} but got {other_decoded_value} for {other_type}"
|
@@ -364,7 +369,9 @@ def test_decode_scalar_numpy_values() -> None:
|
|
364
369
|
({"kind": "Float64"}, np.float64, 2.718, np.float64(2.718)),
|
365
370
|
]
|
366
371
|
for src_type, dst_type, input_value, expected in test_cases:
|
367
|
-
decoder = make_engine_value_decoder(
|
372
|
+
decoder = make_engine_value_decoder(
|
373
|
+
["field"], src_type, analyze_type_info(dst_type)
|
374
|
+
)
|
368
375
|
result = decoder(input_value)
|
369
376
|
assert isinstance(result, dst_type)
|
370
377
|
assert result == expected
|
@@ -378,7 +385,9 @@ def test_non_ndarray_vector_decoding() -> None:
|
|
378
385
|
"dimension": None,
|
379
386
|
}
|
380
387
|
dst_type_float = list[np.float64]
|
381
|
-
decoder = make_engine_value_decoder(
|
388
|
+
decoder = make_engine_value_decoder(
|
389
|
+
["field"], src_type, analyze_type_info(dst_type_float)
|
390
|
+
)
|
382
391
|
input_numbers = [1.0, 2.0, 3.0]
|
383
392
|
result = decoder(input_numbers)
|
384
393
|
assert isinstance(result, list)
|
@@ -388,7 +397,9 @@ def test_non_ndarray_vector_decoding() -> None:
|
|
388
397
|
# Test list[Uuid]
|
389
398
|
src_type = {"kind": "Vector", "element_type": {"kind": "Uuid"}, "dimension": None}
|
390
399
|
dst_type_uuid = list[uuid.UUID]
|
391
|
-
decoder = make_engine_value_decoder(
|
400
|
+
decoder = make_engine_value_decoder(
|
401
|
+
["field"], src_type, analyze_type_info(dst_type_uuid)
|
402
|
+
)
|
392
403
|
uuid1 = uuid.uuid4()
|
393
404
|
uuid2 = uuid.uuid4()
|
394
405
|
input_uuids = [uuid1, uuid2]
|
@@ -398,124 +409,15 @@ def test_non_ndarray_vector_decoding() -> None:
|
|
398
409
|
assert result == [uuid1, uuid2]
|
399
410
|
|
400
411
|
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
# All fields match (NamedTuple)
|
411
|
-
(
|
412
|
-
OrderNamedTuple,
|
413
|
-
["O123", "mixed nuts", 25.0, "default_extra"],
|
414
|
-
OrderNamedTuple("O123", "mixed nuts", 25.0, "default_extra"),
|
415
|
-
),
|
416
|
-
# Extra field in engine value (should ignore extra)
|
417
|
-
(
|
418
|
-
Order,
|
419
|
-
["O123", "mixed nuts", 25.0, "default_extra", "unexpected"],
|
420
|
-
Order("O123", "mixed nuts", 25.0, "default_extra"),
|
421
|
-
),
|
422
|
-
(
|
423
|
-
OrderNamedTuple,
|
424
|
-
["O123", "mixed nuts", 25.0, "default_extra", "unexpected"],
|
425
|
-
OrderNamedTuple("O123", "mixed nuts", 25.0, "default_extra"),
|
426
|
-
),
|
427
|
-
# Fewer fields in engine value (should fill with default)
|
428
|
-
(
|
429
|
-
Order,
|
430
|
-
["O123", "mixed nuts", 0.0, "default_extra"],
|
431
|
-
Order("O123", "mixed nuts", 0.0, "default_extra"),
|
432
|
-
),
|
433
|
-
(
|
434
|
-
OrderNamedTuple,
|
435
|
-
["O123", "mixed nuts", 0.0, "default_extra"],
|
436
|
-
OrderNamedTuple("O123", "mixed nuts", 0.0, "default_extra"),
|
437
|
-
),
|
438
|
-
# More fields in engine value (should ignore extra)
|
439
|
-
(
|
440
|
-
Order,
|
441
|
-
["O123", "mixed nuts", 25.0, "unexpected"],
|
442
|
-
Order("O123", "mixed nuts", 25.0, "unexpected"),
|
443
|
-
),
|
444
|
-
(
|
445
|
-
OrderNamedTuple,
|
446
|
-
["O123", "mixed nuts", 25.0, "unexpected"],
|
447
|
-
OrderNamedTuple("O123", "mixed nuts", 25.0, "unexpected"),
|
448
|
-
),
|
449
|
-
# Truly extra field (should ignore the fifth field)
|
450
|
-
(
|
451
|
-
Order,
|
452
|
-
["O123", "mixed nuts", 25.0, "default_extra", "ignored"],
|
453
|
-
Order("O123", "mixed nuts", 25.0, "default_extra"),
|
454
|
-
),
|
455
|
-
(
|
456
|
-
OrderNamedTuple,
|
457
|
-
["O123", "mixed nuts", 25.0, "default_extra", "ignored"],
|
458
|
-
OrderNamedTuple("O123", "mixed nuts", 25.0, "default_extra"),
|
459
|
-
),
|
460
|
-
# Missing optional field in engine value (tags=None)
|
461
|
-
(
|
462
|
-
Customer,
|
463
|
-
["Alice", ["O1", "item1", 10.0, "default_extra"], None],
|
464
|
-
Customer("Alice", Order("O1", "item1", 10.0, "default_extra"), None),
|
465
|
-
),
|
466
|
-
(
|
467
|
-
CustomerNamedTuple,
|
468
|
-
["Alice", ["O1", "item1", 10.0, "default_extra"], None],
|
469
|
-
CustomerNamedTuple(
|
470
|
-
"Alice", OrderNamedTuple("O1", "item1", 10.0, "default_extra"), None
|
471
|
-
),
|
472
|
-
),
|
473
|
-
# Extra field in engine value for Customer (should ignore)
|
474
|
-
(
|
475
|
-
Customer,
|
476
|
-
["Alice", ["O1", "item1", 10.0, "default_extra"], [["vip"]], "extra"],
|
477
|
-
Customer(
|
478
|
-
"Alice", Order("O1", "item1", 10.0, "default_extra"), [Tag("vip")]
|
479
|
-
),
|
480
|
-
),
|
481
|
-
(
|
482
|
-
CustomerNamedTuple,
|
483
|
-
["Alice", ["O1", "item1", 10.0, "default_extra"], [["vip"]], "extra"],
|
484
|
-
CustomerNamedTuple(
|
485
|
-
"Alice",
|
486
|
-
OrderNamedTuple("O1", "item1", 10.0, "default_extra"),
|
487
|
-
[Tag("vip")],
|
488
|
-
),
|
489
|
-
),
|
490
|
-
# Missing optional field with default
|
491
|
-
(
|
492
|
-
Order,
|
493
|
-
["O123", "mixed nuts", 25.0],
|
494
|
-
Order("O123", "mixed nuts", 25.0, "default_extra"),
|
495
|
-
),
|
496
|
-
(
|
497
|
-
OrderNamedTuple,
|
498
|
-
["O123", "mixed nuts", 25.0],
|
499
|
-
OrderNamedTuple("O123", "mixed nuts", 25.0, "default_extra"),
|
500
|
-
),
|
501
|
-
# Partial optional fields
|
502
|
-
(
|
503
|
-
Customer,
|
504
|
-
["Alice", ["O1", "item1", 10.0]],
|
505
|
-
Customer("Alice", Order("O1", "item1", 10.0, "default_extra"), None),
|
506
|
-
),
|
507
|
-
(
|
508
|
-
CustomerNamedTuple,
|
509
|
-
["Alice", ["O1", "item1", 10.0]],
|
510
|
-
CustomerNamedTuple(
|
511
|
-
"Alice", OrderNamedTuple("O1", "item1", 10.0, "default_extra"), None
|
512
|
-
),
|
513
|
-
),
|
514
|
-
],
|
515
|
-
)
|
516
|
-
def test_struct_decoder_cases(data_type: Any, engine_val: Any, expected: Any) -> None:
|
517
|
-
decoder = build_engine_value_decoder(data_type)
|
518
|
-
assert decoder(engine_val) == expected
|
412
|
+
def test_roundtrip_struct() -> None:
|
413
|
+
validate_full_roundtrip(
|
414
|
+
Order("O123", "mixed nuts", 25.0, "default_extra"),
|
415
|
+
Order,
|
416
|
+
)
|
417
|
+
validate_full_roundtrip(
|
418
|
+
OrderNamedTuple("O123", "mixed nuts", 25.0, "default_extra"),
|
419
|
+
OrderNamedTuple,
|
420
|
+
)
|
519
421
|
|
520
422
|
|
521
423
|
def test_make_engine_value_decoder_list_of_struct() -> None:
|
@@ -974,7 +876,9 @@ def test_decode_nullable_ndarray_none_or_value_input() -> None:
|
|
974
876
|
"dimension": None,
|
975
877
|
}
|
976
878
|
dst_annotation = NDArrayFloat32Type | None
|
977
|
-
decoder = make_engine_value_decoder(
|
879
|
+
decoder = make_engine_value_decoder(
|
880
|
+
[], src_type_dict, analyze_type_info(dst_annotation)
|
881
|
+
)
|
978
882
|
|
979
883
|
none_engine_value = None
|
980
884
|
decoded_array = decoder(none_engine_value)
|
@@ -997,7 +901,9 @@ def test_decode_vector_string() -> None:
|
|
997
901
|
"element_type": {"kind": "Str"},
|
998
902
|
"dimension": None,
|
999
903
|
}
|
1000
|
-
decoder = make_engine_value_decoder(
|
904
|
+
decoder = make_engine_value_decoder(
|
905
|
+
[], src_type_dict, analyze_type_info(Vector[str])
|
906
|
+
)
|
1001
907
|
assert decoder(["hello", "world"]) == ["hello", "world"]
|
1002
908
|
|
1003
909
|
|
@@ -1008,7 +914,9 @@ def test_decode_error_non_nullable_or_non_list_vector() -> None:
|
|
1008
914
|
"element_type": {"kind": "Float32"},
|
1009
915
|
"dimension": None,
|
1010
916
|
}
|
1011
|
-
decoder = make_engine_value_decoder(
|
917
|
+
decoder = make_engine_value_decoder(
|
918
|
+
[], src_type_dict, analyze_type_info(NDArrayFloat32Type)
|
919
|
+
)
|
1012
920
|
with pytest.raises(ValueError, match="Received null for non-nullable vector"):
|
1013
921
|
decoder(None)
|
1014
922
|
with pytest.raises(TypeError, match="Expected NDArray or list for vector"):
|
@@ -1489,3 +1397,41 @@ def test_roundtrip_ktable_with_list_fields() -> None:
|
|
1489
1397
|
|
1490
1398
|
# Test Any annotation
|
1491
1399
|
validate_full_roundtrip(teams, dict[str, Team], (expected_dict_dict, Any))
|
1400
|
+
|
1401
|
+
|
1402
|
+
def test_auto_default_for_supported_and_unsupported_types() -> None:
|
1403
|
+
@dataclass
|
1404
|
+
class Base:
|
1405
|
+
a: int
|
1406
|
+
|
1407
|
+
@dataclass
|
1408
|
+
class NullableField:
|
1409
|
+
a: int
|
1410
|
+
b: int | None
|
1411
|
+
|
1412
|
+
@dataclass
|
1413
|
+
class LTableField:
|
1414
|
+
a: int
|
1415
|
+
b: list[Base]
|
1416
|
+
|
1417
|
+
@dataclass
|
1418
|
+
class KTableField:
|
1419
|
+
a: int
|
1420
|
+
b: dict[str, Base]
|
1421
|
+
|
1422
|
+
@dataclass
|
1423
|
+
class UnsupportedField:
|
1424
|
+
a: int
|
1425
|
+
b: int
|
1426
|
+
|
1427
|
+
validate_full_roundtrip(NullableField(1, None), NullableField)
|
1428
|
+
|
1429
|
+
validate_full_roundtrip(LTableField(1, []), LTableField)
|
1430
|
+
|
1431
|
+
# validate_full_roundtrip(KTableField(1, {}), KTableField)
|
1432
|
+
|
1433
|
+
with pytest.raises(
|
1434
|
+
ValueError,
|
1435
|
+
match=r"Field 'b' \(type <class 'int'>\) without default value is missing in input: ",
|
1436
|
+
):
|
1437
|
+
build_engine_value_decoder(Base, UnsupportedField)
|
cocoindex/tests/test_typing.py
CHANGED
@@ -13,6 +13,7 @@ from cocoindex.typing import (
|
|
13
13
|
AnalyzedDictType,
|
14
14
|
AnalyzedListType,
|
15
15
|
AnalyzedStructType,
|
16
|
+
AnalyzedUnknownType,
|
16
17
|
AnalyzedTypeInfo,
|
17
18
|
TypeAttr,
|
18
19
|
TypeKind,
|
@@ -422,15 +423,7 @@ def test_annotated_list_with_type_kind() -> None:
|
|
422
423
|
assert result.variant.kind == "Struct"
|
423
424
|
|
424
425
|
|
425
|
-
def
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
):
|
430
|
-
analyze_type_info(set)
|
431
|
-
|
432
|
-
with pytest.raises(
|
433
|
-
ValueError,
|
434
|
-
match="Unsupported as a specific type annotation for CocoIndex data type.*: <class 'numpy.complex64'>",
|
435
|
-
):
|
436
|
-
Vector[np.complex64]
|
426
|
+
def test_unknown_type() -> None:
|
427
|
+
typ = set
|
428
|
+
result = analyze_type_info(typ)
|
429
|
+
assert isinstance(result.variant, AnalyzedUnknownType)
|
cocoindex/typing.py
CHANGED
@@ -313,11 +313,11 @@ def analyze_type_info(t: Any) -> AnalyzedTypeInfo:
|
|
313
313
|
kind = "OffsetDateTime"
|
314
314
|
elif t is datetime.timedelta:
|
315
315
|
kind = "TimeDelta"
|
316
|
+
|
317
|
+
if kind is None:
|
318
|
+
variant = AnalyzedUnknownType()
|
316
319
|
else:
|
317
|
-
|
318
|
-
f"Unsupported as a specific type annotation for CocoIndex data type (https://cocoindex.io/docs/core/data_types): {t}"
|
319
|
-
)
|
320
|
-
variant = AnalyzedBasicType(kind=kind)
|
320
|
+
variant = AnalyzedBasicType(kind=kind)
|
321
321
|
|
322
322
|
return AnalyzedTypeInfo(
|
323
323
|
core_type=core_type,
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: cocoindex
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.74
|
4
4
|
Requires-Dist: click>=8.1.8
|
5
5
|
Requires-Dist: rich>=14.0.0
|
6
6
|
Requires-Dist: python-dotenv>=1.1.0
|
@@ -11,7 +11,7 @@ Requires-Dist: ruff ; extra == 'dev'
|
|
11
11
|
Requires-Dist: mypy ; extra == 'dev'
|
12
12
|
Requires-Dist: pre-commit ; extra == 'dev'
|
13
13
|
Requires-Dist: sentence-transformers>=3.3.1 ; extra == 'embeddings'
|
14
|
-
Requires-Dist:
|
14
|
+
Requires-Dist: sentence-transformers>=3.3.1 ; extra == 'all'
|
15
15
|
Provides-Extra: dev
|
16
16
|
Provides-Extra: embeddings
|
17
17
|
Provides-Extra: all
|
@@ -1,18 +1,18 @@
|
|
1
|
-
cocoindex-0.1.
|
2
|
-
cocoindex-0.1.
|
3
|
-
cocoindex-0.1.
|
4
|
-
cocoindex-0.1.
|
1
|
+
cocoindex-0.1.74.dist-info/METADATA,sha256=kkA1L7X7ypbNcSLBtJ92Zp1lYEgEJEfXpR1d1c8V_Lw,11311
|
2
|
+
cocoindex-0.1.74.dist-info/WHEEL,sha256=ZlnHGE4Mo7ps1v16zvcAfrDnL3ds3Klm21sZZ52w3vY,104
|
3
|
+
cocoindex-0.1.74.dist-info/entry_points.txt,sha256=_NretjYVzBdNTn7dK-zgwr7YfG2afz1u1uSE-5bZXF8,46
|
4
|
+
cocoindex-0.1.74.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
5
5
|
cocoindex/__init__.py,sha256=kfTgbh2haepo7kIbzJqfxU6Kx7wPol5_t1SYF2x6cBM,2114
|
6
|
-
cocoindex/_engine.cpython-313-darwin.so,sha256=
|
6
|
+
cocoindex/_engine.cpython-313-darwin.so,sha256=Zcg8ZL5BZwc25OIlgcid6cAweJhz-GqC8f6XP_wdF6Y,65114640
|
7
7
|
cocoindex/auth_registry.py,sha256=PE1-kVkcyC1G2C_V7b1kvYzeq73OFQehWKQP7ln7fJ8,1478
|
8
8
|
cocoindex/cli.py,sha256=-gp639JSyQN6YjnhGqCakIzYoSSqXxQMbxbkcYGP0QY,22359
|
9
|
-
cocoindex/convert.py,sha256=
|
10
|
-
cocoindex/flow.py,sha256=
|
9
|
+
cocoindex/convert.py,sha256=jO95WD8X7AYS5COfRyPiq4t7ewVC1TdRbYpTdHrkCvY,19564
|
10
|
+
cocoindex/flow.py,sha256=C2VwexNxes_7b70jhlp5yAPeBQPtdbKabOp1AarsacA,36184
|
11
11
|
cocoindex/functions.py,sha256=LLu_ausirvqnsx_k3euZpv8sLCpBZ4DF77h2HOzbinE,3109
|
12
12
|
cocoindex/index.py,sha256=j93B9jEvvLXHtpzKWL88SY6wCGEoPgpsQhEGHlyYGFg,540
|
13
13
|
cocoindex/lib.py,sha256=f--9dAYd84CZosbDZqNW0oGbBLsY3dXiUTR1VrfQ_QY,817
|
14
14
|
cocoindex/llm.py,sha256=WxmWUbNcf9HOCM5xkbDeFs9lF67M3mr810B7deDDc-8,673
|
15
|
-
cocoindex/op.py,sha256=
|
15
|
+
cocoindex/op.py,sha256=DNAlZGpGFZWAfRFuxXKmT72kSORdGbirDYsSlD7sDqk,21474
|
16
16
|
cocoindex/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
17
|
cocoindex/runtime.py,sha256=povilB3HH3y1JF-yxKwU-pD8n2WnAqyQxIgvXXHNc60,1080
|
18
18
|
cocoindex/setting.py,sha256=TwhQ6pEeZmvc8ZXlnT9d8Wn8Vz_u7Z5LJUkGsKmKSno,4859
|
@@ -20,11 +20,11 @@ cocoindex/setup.py,sha256=7uIHKN4FOCuoidPXcKyGTrkqpkl9luL49-6UcnMxYzw,3068
|
|
20
20
|
cocoindex/sources.py,sha256=69COA4qbZDipzGYfXv-WJSmicFkA509xIShRGDh6A0A,2083
|
21
21
|
cocoindex/targets.py,sha256=Nfh_tpFd1goTnS_cxBjIs4j9zl3Z4Z1JomAQ1dl3Sic,2796
|
22
22
|
cocoindex/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
23
|
-
cocoindex/tests/test_convert.py,sha256=
|
23
|
+
cocoindex/tests/test_convert.py,sha256=guQ6-AJh4z1NoR60wlm0lJ2R-rOstmFDihDL2oYGouI,46875
|
24
24
|
cocoindex/tests/test_optional_database.py,sha256=snAmkNa6wtOSaxoZE1HgjvL5v_ylitt3Jt_9df4Cgdc,8506
|
25
|
-
cocoindex/tests/test_typing.py,sha256=
|
25
|
+
cocoindex/tests/test_typing.py,sha256=9OF3lO2uSpZBefkEJx7WRbnkXjwQtvlQIeeARYQID68,12391
|
26
26
|
cocoindex/tests/test_validation.py,sha256=X6AQzVs-hVKIXcrHMEMQnhfUE8at7iXQnPq8nHNhZ2Q,4543
|
27
|
-
cocoindex/typing.py,sha256=
|
27
|
+
cocoindex/typing.py,sha256=RK-UYiNkxL9DPjhitFeJVB0b3eV2DsGjOg8V-jrnUYU,13383
|
28
28
|
cocoindex/utils.py,sha256=hUhX-XV6XGCtJSEIpBOuDv6VvqImwPlgBxztBTw7u0U,598
|
29
29
|
cocoindex/validation.py,sha256=PZnJoby4sLbsmPv9fOjOQXuefjfZ7gmtsiTGU8SH-tc,3090
|
30
|
-
cocoindex-0.1.
|
30
|
+
cocoindex-0.1.74.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|