cocoindex 0.1.44__cp313-cp313-macosx_11_0_arm64.whl → 0.1.45__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/__init__.py +2 -1
- cocoindex/_engine.cpython-313-darwin.so +0 -0
- cocoindex/auth_registry.py +7 -3
- cocoindex/cli.py +185 -66
- cocoindex/convert.py +93 -52
- cocoindex/flow.py +302 -131
- cocoindex/functions.py +17 -4
- cocoindex/index.py +6 -0
- cocoindex/lib.py +14 -9
- cocoindex/llm.py +4 -0
- cocoindex/op.py +126 -61
- cocoindex/query.py +40 -17
- cocoindex/runtime.py +9 -4
- cocoindex/setting.py +35 -12
- cocoindex/setup.py +7 -3
- cocoindex/sources.py +3 -1
- cocoindex/storages.py +50 -7
- cocoindex/tests/test_convert.py +255 -63
- cocoindex/typing.py +116 -70
- cocoindex/utils.py +10 -2
- {cocoindex-0.1.44.dist-info → cocoindex-0.1.45.dist-info}/METADATA +3 -1
- cocoindex-0.1.45.dist-info/RECORD +27 -0
- cocoindex-0.1.44.dist-info/RECORD +0 -27
- {cocoindex-0.1.44.dist-info → cocoindex-0.1.45.dist-info}/WHEEL +0 -0
- {cocoindex-0.1.44.dist-info → cocoindex-0.1.45.dist-info}/entry_points.txt +0 -0
- {cocoindex-0.1.44.dist-info → cocoindex-0.1.45.dist-info}/licenses/LICENSE +0 -0
cocoindex/convert.py
CHANGED
@@ -1,35 +1,48 @@
|
|
1
1
|
"""
|
2
2
|
Utilities to convert between Python and engine values.
|
3
3
|
"""
|
4
|
+
|
4
5
|
import dataclasses
|
5
6
|
import datetime
|
6
7
|
import inspect
|
7
8
|
import uuid
|
8
9
|
|
9
10
|
from enum import Enum
|
10
|
-
from typing import Any, Callable, get_origin
|
11
|
-
from .typing import
|
11
|
+
from typing import Any, Callable, get_origin, Mapping
|
12
|
+
from .typing import (
|
13
|
+
analyze_type_info,
|
14
|
+
encode_enriched_type,
|
15
|
+
is_namedtuple_type,
|
16
|
+
TABLE_TYPES,
|
17
|
+
KEY_FIELD_NAME,
|
18
|
+
)
|
12
19
|
|
13
20
|
|
14
21
|
def encode_engine_value(value: Any) -> Any:
|
15
22
|
"""Encode a Python value to an engine value."""
|
16
23
|
if dataclasses.is_dataclass(value):
|
17
|
-
return [
|
24
|
+
return [
|
25
|
+
encode_engine_value(getattr(value, f.name))
|
26
|
+
for f in dataclasses.fields(value)
|
27
|
+
]
|
18
28
|
if is_namedtuple_type(type(value)):
|
19
29
|
return [encode_engine_value(getattr(value, name)) for name in value._fields]
|
20
30
|
if isinstance(value, (list, tuple)):
|
21
31
|
return [encode_engine_value(v) for v in value]
|
22
32
|
if isinstance(value, dict):
|
23
|
-
return [
|
33
|
+
return [
|
34
|
+
[encode_engine_value(k)] + encode_engine_value(v) for k, v in value.items()
|
35
|
+
]
|
24
36
|
if isinstance(value, uuid.UUID):
|
25
37
|
return value.bytes
|
26
38
|
return value
|
27
39
|
|
40
|
+
|
28
41
|
def make_engine_value_decoder(
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
42
|
+
field_path: list[str],
|
43
|
+
src_type: dict[str, Any],
|
44
|
+
dst_annotation: Any,
|
45
|
+
) -> Callable[[Any], Any]:
|
33
46
|
"""
|
34
47
|
Make a decoder from an engine value to a Python value.
|
35
48
|
|
@@ -42,12 +55,18 @@ def make_engine_value_decoder(
|
|
42
55
|
A decoder from an engine value to a Python value.
|
43
56
|
"""
|
44
57
|
|
45
|
-
src_type_kind = src_type[
|
58
|
+
src_type_kind = src_type["kind"]
|
46
59
|
|
47
|
-
if
|
48
|
-
|
49
|
-
|
50
|
-
|
60
|
+
if (
|
61
|
+
dst_annotation is None
|
62
|
+
or dst_annotation is inspect.Parameter.empty
|
63
|
+
or dst_annotation is Any
|
64
|
+
):
|
65
|
+
if src_type_kind == "Struct" or src_type_kind in TABLE_TYPES:
|
66
|
+
raise ValueError(
|
67
|
+
f"Missing type annotation for `{''.join(field_path)}`."
|
68
|
+
f"It's required for {src_type_kind} type."
|
69
|
+
)
|
51
70
|
return lambda value: value
|
52
71
|
|
53
72
|
dst_type_info = analyze_type_info(dst_annotation)
|
@@ -55,96 +74,118 @@ def make_engine_value_decoder(
|
|
55
74
|
if src_type_kind != dst_type_info.kind:
|
56
75
|
raise ValueError(
|
57
76
|
f"Type mismatch for `{''.join(field_path)}`: "
|
58
|
-
f"passed in {src_type_kind}, declared {dst_annotation} ({dst_type_info.kind})"
|
77
|
+
f"passed in {src_type_kind}, declared {dst_annotation} ({dst_type_info.kind})"
|
78
|
+
)
|
59
79
|
|
60
80
|
if dst_type_info.struct_type is not None:
|
61
81
|
return _make_engine_struct_value_decoder(
|
62
|
-
field_path, src_type[
|
82
|
+
field_path, src_type["fields"], dst_type_info.struct_type
|
83
|
+
)
|
63
84
|
|
64
85
|
if src_type_kind in TABLE_TYPES:
|
65
|
-
field_path.append(
|
86
|
+
field_path.append("[*]")
|
66
87
|
elem_type_info = analyze_type_info(dst_type_info.elem_type)
|
67
88
|
if elem_type_info.struct_type is None:
|
68
|
-
raise ValueError(
|
69
|
-
|
70
|
-
|
89
|
+
raise ValueError(
|
90
|
+
f"Type mismatch for `{''.join(field_path)}`: "
|
91
|
+
f"declared `{dst_type_info.kind}`, a dataclass or NamedTuple type expected"
|
92
|
+
)
|
93
|
+
engine_fields_schema = src_type["row"]["fields"]
|
71
94
|
if elem_type_info.key_type is not None:
|
72
95
|
key_field_schema = engine_fields_schema[0]
|
73
96
|
field_path.append(f".{key_field_schema.get('name', KEY_FIELD_NAME)}")
|
74
97
|
key_decoder = make_engine_value_decoder(
|
75
|
-
field_path, key_field_schema[
|
98
|
+
field_path, key_field_schema["type"], elem_type_info.key_type
|
99
|
+
)
|
76
100
|
field_path.pop()
|
77
101
|
value_decoder = _make_engine_struct_value_decoder(
|
78
|
-
field_path, engine_fields_schema[1:], elem_type_info.struct_type
|
79
|
-
|
102
|
+
field_path, engine_fields_schema[1:], elem_type_info.struct_type
|
103
|
+
)
|
104
|
+
|
105
|
+
def decode(value: Any) -> Any | None:
|
80
106
|
if value is None:
|
81
107
|
return None
|
82
108
|
return {key_decoder(v[0]): value_decoder(v[1:]) for v in value}
|
83
109
|
else:
|
84
110
|
elem_decoder = _make_engine_struct_value_decoder(
|
85
|
-
field_path, engine_fields_schema, elem_type_info.struct_type
|
86
|
-
|
111
|
+
field_path, engine_fields_schema, elem_type_info.struct_type
|
112
|
+
)
|
113
|
+
|
114
|
+
def decode(value: Any) -> Any | None:
|
87
115
|
if value is None:
|
88
116
|
return None
|
89
117
|
return [elem_decoder(v) for v in value]
|
118
|
+
|
90
119
|
field_path.pop()
|
91
120
|
return decode
|
92
121
|
|
93
|
-
if src_type_kind ==
|
122
|
+
if src_type_kind == "Uuid":
|
94
123
|
return lambda value: uuid.UUID(bytes=value)
|
95
124
|
|
96
125
|
return lambda value: value
|
97
126
|
|
127
|
+
|
98
128
|
def _make_engine_struct_value_decoder(
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
129
|
+
field_path: list[str],
|
130
|
+
src_fields: list[dict[str, Any]],
|
131
|
+
dst_struct_type: type,
|
132
|
+
) -> Callable[[list[Any]], Any]:
|
103
133
|
"""Make a decoder from an engine field values to a Python value."""
|
104
134
|
|
105
|
-
src_name_to_idx = {f[
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
if is_dataclass:
|
135
|
+
src_name_to_idx = {f["name"]: i for i, f in enumerate(src_fields)}
|
136
|
+
|
137
|
+
parameters: Mapping[str, inspect.Parameter]
|
138
|
+
if dataclasses.is_dataclass(dst_struct_type):
|
111
139
|
parameters = inspect.signature(dst_struct_type).parameters
|
112
|
-
elif
|
113
|
-
defaults = getattr(dst_struct_type,
|
140
|
+
elif is_namedtuple_type(dst_struct_type):
|
141
|
+
defaults = getattr(dst_struct_type, "_field_defaults", {})
|
142
|
+
fields = getattr(dst_struct_type, "_fields", ())
|
114
143
|
parameters = {
|
115
144
|
name: inspect.Parameter(
|
116
145
|
name=name,
|
117
146
|
kind=inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
118
147
|
default=defaults.get(name, inspect.Parameter.empty),
|
119
|
-
annotation=dst_struct_type.__annotations__.get(
|
148
|
+
annotation=dst_struct_type.__annotations__.get(
|
149
|
+
name, inspect.Parameter.empty
|
150
|
+
),
|
120
151
|
)
|
121
|
-
for name in
|
152
|
+
for name in fields
|
122
153
|
}
|
123
154
|
else:
|
124
155
|
raise ValueError(f"Unsupported struct type: {dst_struct_type}")
|
125
156
|
|
126
|
-
def make_closure_for_value(
|
157
|
+
def make_closure_for_value(
|
158
|
+
name: str, param: inspect.Parameter
|
159
|
+
) -> Callable[[list[Any]], Any]:
|
127
160
|
src_idx = src_name_to_idx.get(name)
|
128
161
|
if src_idx is not None:
|
129
|
-
field_path.append(f
|
162
|
+
field_path.append(f".{name}")
|
130
163
|
field_decoder = make_engine_value_decoder(
|
131
|
-
field_path, src_fields[src_idx][
|
164
|
+
field_path, src_fields[src_idx]["type"], param.annotation
|
165
|
+
)
|
132
166
|
field_path.pop()
|
133
|
-
return
|
167
|
+
return (
|
168
|
+
lambda values: field_decoder(values[src_idx])
|
169
|
+
if len(values) > src_idx
|
170
|
+
else param.default
|
171
|
+
)
|
134
172
|
|
135
173
|
default_value = param.default
|
136
174
|
if default_value is inspect.Parameter.empty:
|
137
175
|
raise ValueError(
|
138
|
-
f"Field without default value is missing in input: {''.join(field_path)}"
|
176
|
+
f"Field without default value is missing in input: {''.join(field_path)}"
|
177
|
+
)
|
139
178
|
|
140
179
|
return lambda _: default_value
|
141
180
|
|
142
181
|
field_value_decoder = [
|
143
|
-
make_closure_for_value(name, param)
|
144
|
-
|
182
|
+
make_closure_for_value(name, param) for (name, param) in parameters.items()
|
183
|
+
]
|
145
184
|
|
146
185
|
return lambda values: dst_struct_type(
|
147
|
-
*(decoder(values) for decoder in field_value_decoder)
|
186
|
+
*(decoder(values) for decoder in field_value_decoder)
|
187
|
+
)
|
188
|
+
|
148
189
|
|
149
190
|
def dump_engine_object(v: Any) -> Any:
|
150
191
|
"""Recursively dump an object for engine. Engine side uses `Pythonized` to catch."""
|
@@ -158,14 +199,14 @@ def dump_engine_object(v: Any) -> Any:
|
|
158
199
|
total_secs = v.total_seconds()
|
159
200
|
secs = int(total_secs)
|
160
201
|
nanos = int((total_secs - secs) * 1e9)
|
161
|
-
return {
|
162
|
-
elif hasattr(v,
|
202
|
+
return {"secs": secs, "nanos": nanos}
|
203
|
+
elif hasattr(v, "__dict__"):
|
163
204
|
s = {k: dump_engine_object(v) for k, v in v.__dict__.items()}
|
164
|
-
if hasattr(v,
|
165
|
-
s[
|
205
|
+
if hasattr(v, "kind") and "kind" not in s:
|
206
|
+
s["kind"] = v.kind
|
166
207
|
return s
|
167
208
|
elif isinstance(v, (list, tuple)):
|
168
209
|
return [dump_engine_object(item) for item in v]
|
169
210
|
elif isinstance(v, dict):
|
170
211
|
return {k: dump_engine_object(v) for k, v in v.items()}
|
171
|
-
return v
|
212
|
+
return v
|