cocoindex 0.1.63__cp311-cp311-macosx_11_0_arm64.whl → 0.1.65__cp311-cp311-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/__init__.py +3 -0
- cocoindex/_engine.cpython-311-darwin.so +0 -0
- cocoindex/convert.py +10 -3
- cocoindex/flow.py +65 -16
- cocoindex/setting.py +39 -2
- cocoindex/tests/test_convert.py +135 -11
- cocoindex/typing.py +4 -4
- {cocoindex-0.1.63.dist-info → cocoindex-0.1.65.dist-info}/METADATA +1 -1
- {cocoindex-0.1.63.dist-info → cocoindex-0.1.65.dist-info}/RECORD +12 -12
- {cocoindex-0.1.63.dist-info → cocoindex-0.1.65.dist-info}/WHEEL +1 -1
- {cocoindex-0.1.63.dist-info → cocoindex-0.1.65.dist-info}/entry_points.txt +0 -0
- {cocoindex-0.1.63.dist-info → cocoindex-0.1.65.dist-info}/licenses/LICENSE +0 -0
cocoindex/__init__.py
CHANGED
@@ -11,6 +11,7 @@ from .flow import FlowBuilder, DataScope, DataSlice, Flow, transform_flow
|
|
11
11
|
from .flow import flow_def
|
12
12
|
from .flow import EvaluateAndDumpOptions, GeneratedField
|
13
13
|
from .flow import FlowLiveUpdater, FlowLiveUpdaterOptions
|
14
|
+
from .flow import add_flow_def, remove_flow
|
14
15
|
from .flow import update_all_flows_async, setup_all_flows, drop_all_flows
|
15
16
|
from .lib import init, start_server, stop
|
16
17
|
from .llm import LlmSpec, LlmApiType
|
@@ -52,6 +53,8 @@ __all__ = [
|
|
52
53
|
"GeneratedField",
|
53
54
|
"FlowLiveUpdater",
|
54
55
|
"FlowLiveUpdaterOptions",
|
56
|
+
"add_flow_def",
|
57
|
+
"remove_flow",
|
55
58
|
"update_all_flows_async",
|
56
59
|
"setup_all_flows",
|
57
60
|
"drop_all_flows",
|
Binary file
|
cocoindex/convert.py
CHANGED
@@ -18,6 +18,7 @@ from .typing import (
|
|
18
18
|
encode_enriched_type,
|
19
19
|
extract_ndarray_scalar_dtype,
|
20
20
|
is_namedtuple_type,
|
21
|
+
is_struct_type,
|
21
22
|
)
|
22
23
|
|
23
24
|
|
@@ -37,9 +38,15 @@ def encode_engine_value(value: Any) -> Any:
|
|
37
38
|
if isinstance(value, (list, tuple)):
|
38
39
|
return [encode_engine_value(v) for v in value]
|
39
40
|
if isinstance(value, dict):
|
40
|
-
|
41
|
-
|
42
|
-
|
41
|
+
if not value:
|
42
|
+
return {}
|
43
|
+
|
44
|
+
first_val = next(iter(value.values()))
|
45
|
+
if is_struct_type(type(first_val)): # KTable
|
46
|
+
return [
|
47
|
+
[encode_engine_value(k)] + encode_engine_value(v)
|
48
|
+
for k, v in value.items()
|
49
|
+
]
|
43
50
|
return value
|
44
51
|
|
45
52
|
|
cocoindex/flow.py
CHANGED
@@ -198,18 +198,42 @@ class DataSlice(Generic[T]):
|
|
198
198
|
raise KeyError(field_name)
|
199
199
|
return DataSlice(_DataSliceState(self._state.flow_builder_state, field_slice))
|
200
200
|
|
201
|
-
def row(
|
201
|
+
def row(
|
202
|
+
self,
|
203
|
+
/,
|
204
|
+
*,
|
205
|
+
max_inflight_rows: int | None = None,
|
206
|
+
max_inflight_bytes: int | None = None,
|
207
|
+
) -> DataScope:
|
202
208
|
"""
|
203
209
|
Return a scope representing each row of the table.
|
204
210
|
"""
|
205
|
-
row_scope = self._state.
|
211
|
+
row_scope = self._state.flow_builder_state.engine_flow_builder.for_each(
|
212
|
+
self._state.engine_data_slice,
|
213
|
+
execution_options=dump_engine_object(
|
214
|
+
_ExecutionOptions(
|
215
|
+
max_inflight_rows=max_inflight_rows,
|
216
|
+
max_inflight_bytes=max_inflight_bytes,
|
217
|
+
),
|
218
|
+
),
|
219
|
+
)
|
206
220
|
return DataScope(self._state.flow_builder_state, row_scope)
|
207
221
|
|
208
|
-
def for_each(
|
222
|
+
def for_each(
|
223
|
+
self,
|
224
|
+
f: Callable[[DataScope], None],
|
225
|
+
/,
|
226
|
+
*,
|
227
|
+
max_inflight_rows: int | None = None,
|
228
|
+
max_inflight_bytes: int | None = None,
|
229
|
+
) -> None:
|
209
230
|
"""
|
210
231
|
Apply a function to each row of the collection.
|
211
232
|
"""
|
212
|
-
with self.row(
|
233
|
+
with self.row(
|
234
|
+
max_inflight_rows=max_inflight_rows,
|
235
|
+
max_inflight_bytes=max_inflight_bytes,
|
236
|
+
) as scope:
|
213
237
|
f(scope)
|
214
238
|
|
215
239
|
def transform(
|
@@ -418,7 +442,8 @@ class _SourceRefreshOptions:
|
|
418
442
|
|
419
443
|
@dataclass
|
420
444
|
class _ExecutionOptions:
|
421
|
-
|
445
|
+
max_inflight_rows: int | None = None
|
446
|
+
max_inflight_bytes: int | None = None
|
422
447
|
|
423
448
|
|
424
449
|
class FlowBuilder:
|
@@ -444,7 +469,8 @@ class FlowBuilder:
|
|
444
469
|
*,
|
445
470
|
name: str | None = None,
|
446
471
|
refresh_interval: datetime.timedelta | None = None,
|
447
|
-
|
472
|
+
max_inflight_rows: int | None = None,
|
473
|
+
max_inflight_bytes: int | None = None,
|
448
474
|
) -> DataSlice[T]:
|
449
475
|
"""
|
450
476
|
Import a source to the flow.
|
@@ -464,7 +490,10 @@ class FlowBuilder:
|
|
464
490
|
_SourceRefreshOptions(refresh_interval=refresh_interval)
|
465
491
|
),
|
466
492
|
execution_options=dump_engine_object(
|
467
|
-
_ExecutionOptions(
|
493
|
+
_ExecutionOptions(
|
494
|
+
max_inflight_rows=max_inflight_rows,
|
495
|
+
max_inflight_bytes=max_inflight_bytes,
|
496
|
+
)
|
468
497
|
),
|
469
498
|
),
|
470
499
|
name,
|
@@ -595,7 +624,7 @@ class Flow:
|
|
595
624
|
|
596
625
|
_name: str
|
597
626
|
_full_name: str
|
598
|
-
_lazy_engine_flow: Callable[[], _engine.Flow]
|
627
|
+
_lazy_engine_flow: Callable[[], _engine.Flow] | None
|
599
628
|
|
600
629
|
def __init__(
|
601
630
|
self, name: str, full_name: str, engine_flow_creator: Callable[[], _engine.Flow]
|
@@ -635,18 +664,18 @@ class Flow:
|
|
635
664
|
return tree
|
636
665
|
|
637
666
|
def _get_spec(self, verbose: bool = False) -> _engine.RenderedSpec:
|
638
|
-
return self.
|
667
|
+
return self.internal_flow().get_spec(
|
639
668
|
output_mode="verbose" if verbose else "concise"
|
640
669
|
)
|
641
670
|
|
642
671
|
def _get_schema(self) -> list[tuple[str, str, str]]:
|
643
|
-
return cast(list[tuple[str, str, str]], self.
|
672
|
+
return cast(list[tuple[str, str, str]], self.internal_flow().get_schema())
|
644
673
|
|
645
674
|
def __str__(self) -> str:
|
646
675
|
return str(self._get_spec())
|
647
676
|
|
648
677
|
def __repr__(self) -> str:
|
649
|
-
return repr(self.
|
678
|
+
return repr(self.internal_flow())
|
650
679
|
|
651
680
|
@property
|
652
681
|
def name(self) -> str:
|
@@ -686,12 +715,14 @@ class Flow:
|
|
686
715
|
"""
|
687
716
|
Evaluate the flow and dump flow outputs to files.
|
688
717
|
"""
|
689
|
-
return self.
|
718
|
+
return self.internal_flow().evaluate_and_dump(dump_engine_object(options))
|
690
719
|
|
691
720
|
def internal_flow(self) -> _engine.Flow:
|
692
721
|
"""
|
693
722
|
Get the engine flow.
|
694
723
|
"""
|
724
|
+
if self._lazy_engine_flow is None:
|
725
|
+
raise RuntimeError(f"Flow {self.full_name} is already removed")
|
695
726
|
return self._lazy_engine_flow()
|
696
727
|
|
697
728
|
async def internal_flow_async(self) -> _engine.Flow:
|
@@ -702,13 +733,13 @@ class Flow:
|
|
702
733
|
|
703
734
|
def setup(self, report_to_stdout: bool = False) -> None:
|
704
735
|
"""
|
705
|
-
Setup the flow.
|
736
|
+
Setup persistent backends of the flow.
|
706
737
|
"""
|
707
738
|
execution_context.run(self.setup_async(report_to_stdout=report_to_stdout))
|
708
739
|
|
709
740
|
async def setup_async(self, report_to_stdout: bool = False) -> None:
|
710
741
|
"""
|
711
|
-
Setup the flow. The async version.
|
742
|
+
Setup persistent backends of the flow. The async version.
|
712
743
|
"""
|
713
744
|
await make_setup_bundle([self]).describe_and_apply_async(
|
714
745
|
report_to_stdout=report_to_stdout
|
@@ -716,13 +747,18 @@ class Flow:
|
|
716
747
|
|
717
748
|
def drop(self, report_to_stdout: bool = False) -> None:
|
718
749
|
"""
|
719
|
-
Drop the flow.
|
750
|
+
Drop persistent backends of the flow.
|
751
|
+
|
752
|
+
The current instance is still valid after it's called.
|
753
|
+
For example, you can still call `setup()` after it, to setup the persistent backends again.
|
754
|
+
|
755
|
+
Call `cocoindex.remove_flow()` if you want to remove the flow from the current process.
|
720
756
|
"""
|
721
757
|
execution_context.run(self.drop_async(report_to_stdout=report_to_stdout))
|
722
758
|
|
723
759
|
async def drop_async(self, report_to_stdout: bool = False) -> None:
|
724
760
|
"""
|
725
|
-
Drop the flow. The async version.
|
761
|
+
Drop persistent backends of the flow. The async version.
|
726
762
|
"""
|
727
763
|
await make_drop_bundle([self]).describe_and_apply_async(
|
728
764
|
report_to_stdout=report_to_stdout
|
@@ -776,6 +812,19 @@ def add_flow_def(name: str, fl_def: Callable[[FlowBuilder, DataScope], None]) ->
|
|
776
812
|
return fl
|
777
813
|
|
778
814
|
|
815
|
+
def remove_flow(fl: Flow) -> None:
|
816
|
+
"""
|
817
|
+
Remove a flow from the current process to free up resources.
|
818
|
+
After it's called, methods of the flow should no longer be called.
|
819
|
+
|
820
|
+
This will NOT touch the persistent backends of the flow.
|
821
|
+
"""
|
822
|
+
_engine.remove_flow_context(fl.full_name)
|
823
|
+
fl._lazy_engine_flow = None # pylint: disable=protected-access
|
824
|
+
with _flows_lock:
|
825
|
+
del _flows[fl.name]
|
826
|
+
|
827
|
+
|
779
828
|
def flow_def(
|
780
829
|
name: str | None = None,
|
781
830
|
) -> Callable[[Callable[[FlowBuilder, DataScope], None]], Flow]:
|
cocoindex/setting.py
CHANGED
@@ -43,6 +43,15 @@ class DatabaseConnectionSpec:
|
|
43
43
|
password: str | None = None
|
44
44
|
|
45
45
|
|
46
|
+
@dataclass
|
47
|
+
class GlobalExecutionOptions:
|
48
|
+
"""Global execution options."""
|
49
|
+
|
50
|
+
# The maximum number of concurrent inflight requests, shared among all sources from all flows.
|
51
|
+
source_max_inflight_rows: int | None = None
|
52
|
+
source_max_inflight_bytes: int | None = None
|
53
|
+
|
54
|
+
|
46
55
|
def _load_field(
|
47
56
|
target: dict[str, Any],
|
48
57
|
name: str,
|
@@ -55,7 +64,15 @@ def _load_field(
|
|
55
64
|
if required:
|
56
65
|
raise ValueError(f"{env_name} is not set")
|
57
66
|
else:
|
58
|
-
|
67
|
+
if parse is None:
|
68
|
+
target[name] = value
|
69
|
+
else:
|
70
|
+
try:
|
71
|
+
target[name] = parse(value)
|
72
|
+
except Exception as e:
|
73
|
+
raise ValueError(
|
74
|
+
f"failed to parse environment variable {env_name}: {value}"
|
75
|
+
) from e
|
59
76
|
|
60
77
|
|
61
78
|
@dataclass
|
@@ -64,6 +81,7 @@ class Settings:
|
|
64
81
|
|
65
82
|
database: DatabaseConnectionSpec | None = None
|
66
83
|
app_namespace: str = ""
|
84
|
+
global_execution_options: GlobalExecutionOptions | None = None
|
67
85
|
|
68
86
|
@classmethod
|
69
87
|
def from_env(cls) -> Self:
|
@@ -79,9 +97,28 @@ class Settings:
|
|
79
97
|
else:
|
80
98
|
database = None
|
81
99
|
|
100
|
+
exec_kwargs: dict[str, Any] = dict()
|
101
|
+
_load_field(
|
102
|
+
exec_kwargs,
|
103
|
+
"source_max_inflight_rows",
|
104
|
+
"COCOINDEX_SOURCE_MAX_INFLIGHT_ROWS",
|
105
|
+
parse=int,
|
106
|
+
)
|
107
|
+
_load_field(
|
108
|
+
exec_kwargs,
|
109
|
+
"source_max_inflight_bytes",
|
110
|
+
"COCOINDEX_SOURCE_MAX_INFLIGHT_BYTES",
|
111
|
+
parse=int,
|
112
|
+
)
|
113
|
+
global_execution_options = GlobalExecutionOptions(**exec_kwargs)
|
114
|
+
|
82
115
|
app_namespace = os.getenv("COCOINDEX_APP_NAMESPACE", "")
|
83
116
|
|
84
|
-
return cls(
|
117
|
+
return cls(
|
118
|
+
database=database,
|
119
|
+
app_namespace=app_namespace,
|
120
|
+
global_execution_options=global_execution_options,
|
121
|
+
)
|
85
122
|
|
86
123
|
|
87
124
|
@dataclass
|
cocoindex/tests/test_convert.py
CHANGED
@@ -93,7 +93,7 @@ def validate_full_roundtrip_to(
|
|
93
93
|
def eq(a: Any, b: Any) -> bool:
|
94
94
|
if isinstance(a, np.ndarray) and isinstance(b, np.ndarray):
|
95
95
|
return np.array_equal(a, b)
|
96
|
-
return type(a)
|
96
|
+
return type(a) is type(b) and not not (a == b)
|
97
97
|
|
98
98
|
encoded_value = encode_engine_value(value)
|
99
99
|
value_type = value_type or type(value)
|
@@ -229,6 +229,11 @@ def test_encode_engine_value_none() -> None:
|
|
229
229
|
|
230
230
|
|
231
231
|
def test_roundtrip_basic_types() -> None:
|
232
|
+
validate_full_roundtrip(b"hello world", bytes, (b"hello world", None))
|
233
|
+
validate_full_roundtrip(b"\x00\x01\x02\xff\xfe", bytes)
|
234
|
+
validate_full_roundtrip("hello", str, ("hello", None))
|
235
|
+
validate_full_roundtrip(True, bool, (True, None))
|
236
|
+
validate_full_roundtrip(False, bool, (False, None))
|
232
237
|
validate_full_roundtrip(
|
233
238
|
42, cocoindex.Int64, (42, int), (np.int64(42), np.int64), (42, None)
|
234
239
|
)
|
@@ -252,10 +257,29 @@ def test_roundtrip_basic_types() -> None:
|
|
252
257
|
)
|
253
258
|
validate_full_roundtrip(np.float32(3.25), np.float32, (3.25, Float32))
|
254
259
|
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
validate_full_roundtrip(
|
260
|
+
|
261
|
+
def test_roundtrip_uuid() -> None:
|
262
|
+
uuid_value = uuid.uuid4()
|
263
|
+
validate_full_roundtrip(uuid_value, uuid.UUID, (uuid_value, None))
|
264
|
+
|
265
|
+
|
266
|
+
def test_roundtrip_range() -> None:
|
267
|
+
r1 = (0, 100)
|
268
|
+
validate_full_roundtrip(r1, cocoindex.Range, (r1, None))
|
269
|
+
r2 = (50, 50)
|
270
|
+
validate_full_roundtrip(r2, cocoindex.Range, (r2, None))
|
271
|
+
r3 = (0, 1_000_000_000)
|
272
|
+
validate_full_roundtrip(r3, cocoindex.Range, (r3, None))
|
273
|
+
|
274
|
+
|
275
|
+
def test_roundtrip_time() -> None:
|
276
|
+
t1 = datetime.time(10, 30, 50, 123456)
|
277
|
+
validate_full_roundtrip(t1, datetime.time, (t1, None))
|
278
|
+
t2 = datetime.time(23, 59, 59)
|
279
|
+
validate_full_roundtrip(t2, datetime.time, (t2, None))
|
280
|
+
t3 = datetime.time(0, 0, 0)
|
281
|
+
validate_full_roundtrip(t3, datetime.time, (t3, None))
|
282
|
+
|
259
283
|
validate_full_roundtrip(
|
260
284
|
datetime.date(2025, 1, 1), datetime.date, (datetime.date(2025, 1, 1), None)
|
261
285
|
)
|
@@ -297,8 +321,38 @@ def test_roundtrip_basic_types() -> None:
|
|
297
321
|
),
|
298
322
|
)
|
299
323
|
|
300
|
-
|
301
|
-
|
324
|
+
|
325
|
+
def test_roundtrip_timedelta() -> None:
|
326
|
+
td1 = datetime.timedelta(
|
327
|
+
days=5, seconds=10, microseconds=123, milliseconds=456, minutes=30, hours=2
|
328
|
+
)
|
329
|
+
validate_full_roundtrip(td1, datetime.timedelta, (td1, None))
|
330
|
+
td2 = datetime.timedelta(days=-5, hours=-2)
|
331
|
+
validate_full_roundtrip(td2, datetime.timedelta, (td2, None))
|
332
|
+
td3 = datetime.timedelta(0)
|
333
|
+
validate_full_roundtrip(td3, datetime.timedelta, (td3, None))
|
334
|
+
|
335
|
+
|
336
|
+
def test_roundtrip_json() -> None:
|
337
|
+
simple_dict = {"key": "value", "number": 123, "bool": True, "float": 1.23}
|
338
|
+
validate_full_roundtrip(simple_dict, cocoindex.Json)
|
339
|
+
|
340
|
+
simple_list = [1, "string", False, None, 4.56]
|
341
|
+
validate_full_roundtrip(simple_list, cocoindex.Json)
|
342
|
+
|
343
|
+
nested_structure = {
|
344
|
+
"name": "Test Json",
|
345
|
+
"version": 1.0,
|
346
|
+
"items": [
|
347
|
+
{"id": 1, "value": "item1"},
|
348
|
+
{"id": 2, "value": None, "props": {"active": True}},
|
349
|
+
],
|
350
|
+
"metadata": None,
|
351
|
+
}
|
352
|
+
validate_full_roundtrip(nested_structure, cocoindex.Json)
|
353
|
+
|
354
|
+
validate_full_roundtrip({}, cocoindex.Json)
|
355
|
+
validate_full_roundtrip([], cocoindex.Json)
|
302
356
|
|
303
357
|
|
304
358
|
def test_decode_scalar_numpy_values() -> None:
|
@@ -675,6 +729,21 @@ def test_roundtrip_union_with_vector() -> None:
|
|
675
729
|
validate_full_roundtrip(value, t, ([1.0, 2.0, 3.0], list[float] | str))
|
676
730
|
|
677
731
|
|
732
|
+
def test_roundtrip_union_with_misc_types() -> None:
|
733
|
+
t_bytes_union = int | bytes | str
|
734
|
+
validate_full_roundtrip(b"test_bytes", t_bytes_union)
|
735
|
+
validate_full_roundtrip(123, t_bytes_union)
|
736
|
+
|
737
|
+
t_range_union = cocoindex.Range | str | bool
|
738
|
+
validate_full_roundtrip((100, 200), t_range_union)
|
739
|
+
validate_full_roundtrip("test_string", t_range_union)
|
740
|
+
|
741
|
+
t_json_union = cocoindex.Json | int | bytes
|
742
|
+
json_dict = {"a": 1, "b": [2, 3]}
|
743
|
+
validate_full_roundtrip(json_dict, t_json_union)
|
744
|
+
validate_full_roundtrip(b"another_byte_string", t_json_union)
|
745
|
+
|
746
|
+
|
678
747
|
def test_roundtrip_ltable() -> None:
|
679
748
|
t = list[Order]
|
680
749
|
value = [Order("O1", "item1", 10.0), Order("O2", "item2", 20.0)]
|
@@ -688,10 +757,26 @@ def test_roundtrip_ltable() -> None:
|
|
688
757
|
validate_full_roundtrip(value_nt, t_nt)
|
689
758
|
|
690
759
|
|
691
|
-
def
|
692
|
-
|
693
|
-
|
694
|
-
|
760
|
+
def test_roundtrip_ktable_various_key_types() -> None:
|
761
|
+
@dataclass
|
762
|
+
class SimpleValue:
|
763
|
+
data: str
|
764
|
+
|
765
|
+
t_bytes_key = dict[bytes, SimpleValue]
|
766
|
+
value_bytes_key = {b"key1": SimpleValue("val1"), b"key2": SimpleValue("val2")}
|
767
|
+
validate_full_roundtrip(value_bytes_key, t_bytes_key)
|
768
|
+
|
769
|
+
t_int_key = dict[int, SimpleValue]
|
770
|
+
value_int_key = {1: SimpleValue("val1"), 2: SimpleValue("val2")}
|
771
|
+
validate_full_roundtrip(value_int_key, t_int_key)
|
772
|
+
|
773
|
+
t_bool_key = dict[bool, SimpleValue]
|
774
|
+
value_bool_key = {True: SimpleValue("val_true"), False: SimpleValue("val_false")}
|
775
|
+
validate_full_roundtrip(value_bool_key, t_bool_key)
|
776
|
+
|
777
|
+
t_str_key = dict[str, Order]
|
778
|
+
value_str_key = {"K1": Order("O1", "item1", 10.0), "K2": Order("O2", "item2", 20.0)}
|
779
|
+
validate_full_roundtrip(value_str_key, t_str_key)
|
695
780
|
|
696
781
|
t_nt = dict[str, OrderNamedTuple]
|
697
782
|
value_nt = {
|
@@ -700,6 +785,27 @@ def test_roundtrip_ktable_str_key() -> None:
|
|
700
785
|
}
|
701
786
|
validate_full_roundtrip(value_nt, t_nt)
|
702
787
|
|
788
|
+
t_range_key = dict[cocoindex.Range, SimpleValue]
|
789
|
+
value_range_key = {
|
790
|
+
(1, 10): SimpleValue("val_range1"),
|
791
|
+
(20, 30): SimpleValue("val_range2"),
|
792
|
+
}
|
793
|
+
validate_full_roundtrip(value_range_key, t_range_key)
|
794
|
+
|
795
|
+
t_date_key = dict[datetime.date, SimpleValue]
|
796
|
+
value_date_key = {
|
797
|
+
datetime.date(2023, 1, 1): SimpleValue("val_date1"),
|
798
|
+
datetime.date(2024, 2, 2): SimpleValue("val_date2"),
|
799
|
+
}
|
800
|
+
validate_full_roundtrip(value_date_key, t_date_key)
|
801
|
+
|
802
|
+
t_uuid_key = dict[uuid.UUID, SimpleValue]
|
803
|
+
value_uuid_key = {
|
804
|
+
uuid.uuid4(): SimpleValue("val_uuid1"),
|
805
|
+
uuid.uuid4(): SimpleValue("val_uuid2"),
|
806
|
+
}
|
807
|
+
validate_full_roundtrip(value_uuid_key, t_uuid_key)
|
808
|
+
|
703
809
|
|
704
810
|
def test_roundtrip_ktable_struct_key() -> None:
|
705
811
|
@dataclass(frozen=True)
|
@@ -990,6 +1096,24 @@ def test_full_roundtrip_vector_numeric_types() -> None:
|
|
990
1096
|
validate_full_roundtrip(value_u64, Vector[np.uint64, Literal[3]])
|
991
1097
|
|
992
1098
|
|
1099
|
+
def test_full_roundtrip_vector_other_types() -> None:
|
1100
|
+
"""Test full roundtrip for Vector with non-numeric basic types."""
|
1101
|
+
uuid_list = [uuid.uuid4(), uuid.uuid4()]
|
1102
|
+
validate_full_roundtrip(uuid_list, Vector[uuid.UUID], (uuid_list, list[uuid.UUID]))
|
1103
|
+
|
1104
|
+
date_list = [datetime.date(2023, 1, 1), datetime.date(2024, 10, 5)]
|
1105
|
+
validate_full_roundtrip(
|
1106
|
+
date_list, Vector[datetime.date], (date_list, list[datetime.date])
|
1107
|
+
)
|
1108
|
+
|
1109
|
+
bool_list = [True, False, True, False]
|
1110
|
+
validate_full_roundtrip(bool_list, Vector[bool], (bool_list, list[bool]))
|
1111
|
+
|
1112
|
+
validate_full_roundtrip([], Vector[uuid.UUID], ([], list[uuid.UUID]))
|
1113
|
+
validate_full_roundtrip([], Vector[datetime.date], ([], list[datetime.date]))
|
1114
|
+
validate_full_roundtrip([], Vector[bool], ([], list[bool]))
|
1115
|
+
|
1116
|
+
|
993
1117
|
def test_roundtrip_vector_no_dimension() -> None:
|
994
1118
|
"""Test full roundtrip for vector types without dimension annotation."""
|
995
1119
|
value_f64 = np.array([1.0, 2.0, 3.0], dtype=np.float64)
|
cocoindex/typing.py
CHANGED
@@ -108,7 +108,7 @@ def is_namedtuple_type(t: type) -> bool:
|
|
108
108
|
return isinstance(t, type) and issubclass(t, tuple) and hasattr(t, "_fields")
|
109
109
|
|
110
110
|
|
111
|
-
def
|
111
|
+
def is_struct_type(t: ElementType | None) -> bool:
|
112
112
|
return isinstance(t, type) and (
|
113
113
|
dataclasses.is_dataclass(t) or is_namedtuple_type(t)
|
114
114
|
)
|
@@ -205,7 +205,7 @@ def analyze_type_info(t: Any) -> AnalyzedTypeInfo:
|
|
205
205
|
union_variant_types: typing.List[ElementType] | None = None
|
206
206
|
key_type: type | None = None
|
207
207
|
np_number_type: type | None = None
|
208
|
-
if
|
208
|
+
if is_struct_type(t):
|
209
209
|
struct_type = t
|
210
210
|
|
211
211
|
if kind is None:
|
@@ -220,7 +220,7 @@ def analyze_type_info(t: Any) -> AnalyzedTypeInfo:
|
|
220
220
|
elem_type = args[0]
|
221
221
|
|
222
222
|
if kind is None:
|
223
|
-
if
|
223
|
+
if is_struct_type(elem_type):
|
224
224
|
kind = "LTable"
|
225
225
|
if vector_info is not None:
|
226
226
|
raise ValueError(
|
@@ -243,7 +243,7 @@ def analyze_type_info(t: Any) -> AnalyzedTypeInfo:
|
|
243
243
|
args = typing.get_args(t)
|
244
244
|
elem_type = (args[0], args[1])
|
245
245
|
kind = "KTable"
|
246
|
-
elif base_type
|
246
|
+
elif base_type in (types.UnionType, typing.Union):
|
247
247
|
possible_types = typing.get_args(t)
|
248
248
|
non_none_types = [
|
249
249
|
arg for arg in possible_types if arg not in (None, types.NoneType)
|
@@ -1,13 +1,13 @@
|
|
1
|
-
cocoindex-0.1.
|
2
|
-
cocoindex-0.1.
|
3
|
-
cocoindex-0.1.
|
4
|
-
cocoindex-0.1.
|
5
|
-
cocoindex/__init__.py,sha256=
|
6
|
-
cocoindex/_engine.cpython-311-darwin.so,sha256=
|
1
|
+
cocoindex-0.1.65.dist-info/METADATA,sha256=q_r8liwwqP1fcDFl0sNkTjty5ZZt6xbGEUFcpOOOR04,10136
|
2
|
+
cocoindex-0.1.65.dist-info/WHEEL,sha256=4POUqOUvk-fNEqEa1NBlmMsgWQGl6FnEg9vsbsvEmNM,104
|
3
|
+
cocoindex-0.1.65.dist-info/entry_points.txt,sha256=_NretjYVzBdNTn7dK-zgwr7YfG2afz1u1uSE-5bZXF8,46
|
4
|
+
cocoindex-0.1.65.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
5
|
+
cocoindex/__init__.py,sha256=TXK1FRqfm9DeDD7MycgDEfNutu8utM-dARQSurD98vc,1993
|
6
|
+
cocoindex/_engine.cpython-311-darwin.so,sha256=DwM7O6Gwxv7FNWZ5VrvcLsEQvdqEgvcWBvR6J6ZOsk4,58266144
|
7
7
|
cocoindex/auth_registry.py,sha256=1XqO7ibjmBBd8i11XSJTvTgdz8p1ptW-ZpuSgo_5zzk,716
|
8
8
|
cocoindex/cli.py,sha256=-gp639JSyQN6YjnhGqCakIzYoSSqXxQMbxbkcYGP0QY,22359
|
9
|
-
cocoindex/convert.py,sha256=
|
10
|
-
cocoindex/flow.py,sha256=
|
9
|
+
cocoindex/convert.py,sha256=qE1Ut_tAwX4wA4WqaWxpyj80-1t6WZ8Oi5_L9Mw5g4k,11393
|
10
|
+
cocoindex/flow.py,sha256=MFPtfJBVTjQ56d7vUn2LvtY30Vg4q2rY6nqvjjJL1kQ,35085
|
11
11
|
cocoindex/functions.py,sha256=IBwvdPpGR-S5mk53HvHpT2GVs15MI9wQznxgOdxA0ac,3202
|
12
12
|
cocoindex/index.py,sha256=j93B9jEvvLXHtpzKWL88SY6wCGEoPgpsQhEGHlyYGFg,540
|
13
13
|
cocoindex/lib.py,sha256=f--9dAYd84CZosbDZqNW0oGbBLsY3dXiUTR1VrfQ_QY,817
|
@@ -15,14 +15,14 @@ cocoindex/llm.py,sha256=0ri8ZRg9_Zf2gyC5xuQ1Kq6kdZUO8r-A5WLnxit5S_4,448
|
|
15
15
|
cocoindex/op.py,sha256=r_Usx7Jqh49Cck3tsYLx2vLRNUZArkQP_g7bIID6LPU,11809
|
16
16
|
cocoindex/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
17
17
|
cocoindex/runtime.py,sha256=bAdHYaXFWiiUWyAgzmKTeaAaRR0D_AmaqVCIdPO-v00,1056
|
18
|
-
cocoindex/setting.py,sha256=
|
18
|
+
cocoindex/setting.py,sha256=ADuv7RaWd9k-m3V0Cfy2jmaCt6DupJCviWdOm0CTiVw,4734
|
19
19
|
cocoindex/setup.py,sha256=7uIHKN4FOCuoidPXcKyGTrkqpkl9luL49-6UcnMxYzw,3068
|
20
20
|
cocoindex/sources.py,sha256=JCnOhv1w4o28e03i7yvo4ESicWYAhckkBg5bQlxNH4U,1330
|
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=efwF-43SFJiu85sQ78Z9k9OaJphTz_es_1cm5BoPO2Y,42565
|
24
24
|
cocoindex/tests/test_optional_database.py,sha256=snAmkNa6wtOSaxoZE1HgjvL5v_ylitt3Jt_9df4Cgdc,8506
|
25
25
|
cocoindex/tests/test_typing.py,sha256=t6UCYShcfonTfjBlGRWPiFGMZ8DGFfABXo6idekPoJE,14757
|
26
|
-
cocoindex/typing.py,sha256=
|
26
|
+
cocoindex/typing.py,sha256=qQ-nSdkHzu8pSxfuR5sGGfoE8nCKqCDb0D9jbmxVt4M,12635
|
27
27
|
cocoindex/utils.py,sha256=hUhX-XV6XGCtJSEIpBOuDv6VvqImwPlgBxztBTw7u0U,598
|
28
|
-
cocoindex-0.1.
|
28
|
+
cocoindex-0.1.65.dist-info/RECORD,,
|
File without changes
|
File without changes
|