cocoindex 0.1.25__cp311-cp311-macosx_11_0_arm64.whl → 0.1.26__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/_engine.cpython-311-darwin.so +0 -0
- cocoindex/cli.py +9 -6
- cocoindex/flow.py +44 -13
- cocoindex/lib.py +2 -1
- cocoindex/llm.py +1 -0
- cocoindex/op.py +3 -2
- cocoindex/runtime.py +12 -4
- cocoindex/storages.py +9 -2
- cocoindex/tests/test_convert.py +169 -4
- {cocoindex-0.1.25.dist-info → cocoindex-0.1.26.dist-info}/METADATA +2 -2
- cocoindex-0.1.26.dist-info/RECORD +24 -0
- cocoindex-0.1.25.dist-info/RECORD +0 -24
- {cocoindex-0.1.25.dist-info → cocoindex-0.1.26.dist-info}/WHEEL +0 -0
- {cocoindex-0.1.25.dist-info → cocoindex-0.1.26.dist-info}/licenses/LICENSE +0 -0
Binary file
|
cocoindex/cli.py
CHANGED
@@ -4,6 +4,7 @@ import datetime
|
|
4
4
|
|
5
5
|
from . import flow, lib
|
6
6
|
from .setup import sync_setup, drop_setup, flow_names_with_setup, apply_setup_changes
|
7
|
+
from .runtime import execution_context
|
7
8
|
|
8
9
|
@click.group()
|
9
10
|
def cli():
|
@@ -113,11 +114,13 @@ def update(flow_name: str | None, live: bool, quiet: bool):
|
|
113
114
|
Update the index to reflect the latest data from data sources.
|
114
115
|
"""
|
115
116
|
options = flow.FlowLiveUpdaterOptions(live_mode=live, print_stats=not quiet)
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
117
|
+
async def _update():
|
118
|
+
if flow_name is None:
|
119
|
+
await flow.update_all_flows(options)
|
120
|
+
else:
|
121
|
+
updater = await flow.FlowLiveUpdater.create(_flow_by_name(flow_name), options)
|
122
|
+
await updater.wait()
|
123
|
+
execution_context.run(_update())
|
121
124
|
|
122
125
|
@cli.command()
|
123
126
|
@click.argument("flow_name", type=str, required=False)
|
@@ -167,7 +170,7 @@ def server(address: str, live_update: bool, quiet: bool, cors_origin: str | None
|
|
167
170
|
lib.start_server(lib.ServerSettings(address=address, cors_origin=cors_origin))
|
168
171
|
if live_update:
|
169
172
|
options = flow.FlowLiveUpdaterOptions(live_mode=True, print_stats=not quiet)
|
170
|
-
|
173
|
+
execution_context.run(flow.update_all_flows(options))
|
171
174
|
input("Press Enter to stop...")
|
172
175
|
|
173
176
|
|
cocoindex/flow.py
CHANGED
@@ -19,7 +19,7 @@ from . import index
|
|
19
19
|
from . import op
|
20
20
|
from .convert import dump_engine_object
|
21
21
|
from .typing import encode_enriched_type
|
22
|
-
from .runtime import
|
22
|
+
from .runtime import execution_context
|
23
23
|
|
24
24
|
class _NameBuilder:
|
25
25
|
_existing_names: set[str]
|
@@ -355,6 +355,12 @@ class FlowBuilder:
|
|
355
355
|
name
|
356
356
|
)
|
357
357
|
|
358
|
+
def declare(self, spec: op.DeclarationSpec):
|
359
|
+
"""
|
360
|
+
Add a declaration to the flow.
|
361
|
+
"""
|
362
|
+
self._state.engine_flow_builder.declare(dump_engine_object(spec))
|
363
|
+
|
358
364
|
@dataclass
|
359
365
|
class FlowLiveUpdaterOptions:
|
360
366
|
"""
|
@@ -369,16 +375,29 @@ class FlowLiveUpdater:
|
|
369
375
|
"""
|
370
376
|
_engine_live_updater: _engine.FlowLiveUpdater
|
371
377
|
|
372
|
-
def __init__(self,
|
373
|
-
|
374
|
-
|
378
|
+
def __init__(self, arg: Flow | _engine.FlowLiveUpdater, options: FlowLiveUpdaterOptions | None = None):
|
379
|
+
if isinstance(arg, _engine.FlowLiveUpdater):
|
380
|
+
self._engine_live_updater = arg
|
381
|
+
else:
|
382
|
+
self._engine_live_updater = execution_context.run(_engine.FlowLiveUpdater(
|
383
|
+
arg.internal_flow(), dump_engine_object(options or FlowLiveUpdaterOptions())))
|
384
|
+
|
385
|
+
@staticmethod
|
386
|
+
async def create(fl: Flow, options: FlowLiveUpdaterOptions | None = None) -> FlowLiveUpdater:
|
387
|
+
"""
|
388
|
+
Create a live updater for a flow.
|
389
|
+
"""
|
390
|
+
engine_live_updater = await _engine.FlowLiveUpdater.create(
|
391
|
+
await fl.ainternal_flow(),
|
392
|
+
dump_engine_object(options or FlowLiveUpdaterOptions()))
|
393
|
+
return FlowLiveUpdater(engine_live_updater)
|
375
394
|
|
376
395
|
def __enter__(self) -> FlowLiveUpdater:
|
377
396
|
return self
|
378
397
|
|
379
398
|
def __exit__(self, exc_type, exc_value, traceback):
|
380
399
|
self.abort()
|
381
|
-
|
400
|
+
execution_context.run(self.wait())
|
382
401
|
|
383
402
|
async def __aenter__(self) -> FlowLiveUpdater:
|
384
403
|
return self
|
@@ -450,7 +469,7 @@ class Flow:
|
|
450
469
|
Update the index defined by the flow.
|
451
470
|
Once the function returns, the indice is fresh up to the moment when the function is called.
|
452
471
|
"""
|
453
|
-
updater = FlowLiveUpdater(self, FlowLiveUpdaterOptions(live_mode=False))
|
472
|
+
updater = await FlowLiveUpdater.create(self, FlowLiveUpdaterOptions(live_mode=False))
|
454
473
|
await updater.wait()
|
455
474
|
return updater.update_stats()
|
456
475
|
|
@@ -466,6 +485,12 @@ class Flow:
|
|
466
485
|
"""
|
467
486
|
return self._lazy_engine_flow()
|
468
487
|
|
488
|
+
async def ainternal_flow(self) -> _engine.Flow:
|
489
|
+
"""
|
490
|
+
Get the engine flow. The async version.
|
491
|
+
"""
|
492
|
+
return await asyncio.to_thread(self.internal_flow)
|
493
|
+
|
469
494
|
def _create_lazy_flow(name: str | None, fl_def: Callable[[FlowBuilder, DataScope], None]) -> Flow:
|
470
495
|
"""
|
471
496
|
Create a flow without really building it yet.
|
@@ -476,7 +501,7 @@ def _create_lazy_flow(name: str | None, fl_def: Callable[[FlowBuilder, DataScope
|
|
476
501
|
root_scope = DataScope(
|
477
502
|
flow_builder_state, flow_builder_state.engine_flow_builder.root_scope())
|
478
503
|
fl_def(FlowBuilder(flow_builder_state), root_scope)
|
479
|
-
return flow_builder_state.engine_flow_builder.build_flow(
|
504
|
+
return flow_builder_state.engine_flow_builder.build_flow(execution_context.event_loop)
|
480
505
|
|
481
506
|
return Flow(_create_engine_flow)
|
482
507
|
|
@@ -523,17 +548,23 @@ def ensure_all_flows_built() -> None:
|
|
523
548
|
"""
|
524
549
|
Ensure all flows are built.
|
525
550
|
"""
|
526
|
-
|
527
|
-
|
528
|
-
|
551
|
+
for fl in flows():
|
552
|
+
fl.internal_flow()
|
553
|
+
|
554
|
+
async def aensure_all_flows_built() -> None:
|
555
|
+
"""
|
556
|
+
Ensure all flows are built.
|
557
|
+
"""
|
558
|
+
for fl in flows():
|
559
|
+
await fl.ainternal_flow()
|
529
560
|
|
530
561
|
async def update_all_flows(options: FlowLiveUpdaterOptions) -> dict[str, _engine.IndexUpdateInfo]:
|
531
562
|
"""
|
532
563
|
Update all flows.
|
533
564
|
"""
|
534
|
-
|
565
|
+
await aensure_all_flows_built()
|
535
566
|
async def _update_flow(fl: Flow) -> _engine.IndexUpdateInfo:
|
536
|
-
updater = FlowLiveUpdater(fl, options)
|
567
|
+
updater = await FlowLiveUpdater.create(fl, options)
|
537
568
|
await updater.wait()
|
538
569
|
return updater.update_stats()
|
539
570
|
fls = flows()
|
@@ -572,7 +603,7 @@ class TransientFlow:
|
|
572
603
|
flow_builder_state.engine_flow_builder.set_direct_output(
|
573
604
|
_data_slice_state(output).engine_data_slice)
|
574
605
|
self._engine_flow = flow_builder_state.engine_flow_builder.build_transient_flow(
|
575
|
-
|
606
|
+
execution_context.event_loop)
|
576
607
|
|
577
608
|
def __str__(self):
|
578
609
|
return str(self._engine_flow)
|
cocoindex/lib.py
CHANGED
@@ -101,7 +101,8 @@ def main_fn(
|
|
101
101
|
try:
|
102
102
|
if _should_run_cli():
|
103
103
|
# Schedule to a separate thread as it invokes nested event loop.
|
104
|
-
return await asyncio.to_thread(_run_cli)
|
104
|
+
# return await asyncio.to_thread(_run_cli)
|
105
|
+
return _run_cli()
|
105
106
|
return await fn(*args, **kwargs)
|
106
107
|
finally:
|
107
108
|
stop()
|
cocoindex/llm.py
CHANGED
cocoindex/op.py
CHANGED
@@ -7,7 +7,6 @@ import inspect
|
|
7
7
|
|
8
8
|
from typing import get_type_hints, Protocol, Any, Callable, Awaitable, dataclass_transform
|
9
9
|
from enum import Enum
|
10
|
-
from functools import partial
|
11
10
|
|
12
11
|
from .typing import encode_enriched_type
|
13
12
|
from .convert import to_engine_value, make_engine_value_converter
|
@@ -18,7 +17,7 @@ class OpCategory(Enum):
|
|
18
17
|
FUNCTION = "function"
|
19
18
|
SOURCE = "source"
|
20
19
|
STORAGE = "storage"
|
21
|
-
|
20
|
+
DECLARATION = "declaration"
|
22
21
|
@dataclass_transform()
|
23
22
|
class SpecMeta(type):
|
24
23
|
"""Meta class for spec classes."""
|
@@ -41,6 +40,8 @@ class FunctionSpec(metaclass=SpecMeta, category=OpCategory.FUNCTION): # pylint:
|
|
41
40
|
class StorageSpec(metaclass=SpecMeta, category=OpCategory.STORAGE): # pylint: disable=too-few-public-methods
|
42
41
|
"""A storage spec. All its subclass can be instantiated similar to a dataclass, i.e. ClassName(field1=value1, field2=value2, ...)"""
|
43
42
|
|
43
|
+
class DeclarationSpec(metaclass=SpecMeta, category=OpCategory.DECLARATION): # pylint: disable=too-few-public-methods
|
44
|
+
"""A declaration spec. All its subclass can be instantiated similar to a dataclass, i.e. ClassName(field1=value1, field2=value2, ...)"""
|
44
45
|
class Executor(Protocol):
|
45
46
|
"""An executor for an operation."""
|
46
47
|
op_category: OpCategory
|
cocoindex/runtime.py
CHANGED
@@ -1,7 +1,12 @@
|
|
1
|
+
"""
|
2
|
+
This module provides a standalone execution runtime for executing coroutines in a thread-safe
|
3
|
+
manner.
|
4
|
+
"""
|
5
|
+
|
1
6
|
import threading
|
2
7
|
import asyncio
|
3
|
-
|
4
|
-
class
|
8
|
+
from typing import Coroutine
|
9
|
+
class _ExecutionContext:
|
5
10
|
_lock: threading.Lock
|
6
11
|
_event_loop: asyncio.AbstractEventLoop | None = None
|
7
12
|
|
@@ -14,8 +19,11 @@ class _OpExecutionContext:
|
|
14
19
|
with self._lock:
|
15
20
|
if self._event_loop is None:
|
16
21
|
self._event_loop = asyncio.new_event_loop()
|
17
|
-
asyncio.set_event_loop(self._event_loop)
|
18
22
|
threading.Thread(target=self._event_loop.run_forever, daemon=True).start()
|
19
23
|
return self._event_loop
|
20
24
|
|
21
|
-
|
25
|
+
def run(self, coro: Coroutine):
|
26
|
+
"""Run a coroutine in the event loop, blocking until it finishes. Return its result."""
|
27
|
+
return asyncio.run_coroutine_threadsafe(coro, self.event_loop).result()
|
28
|
+
|
29
|
+
execution_context = _ExecutionContext()
|
cocoindex/storages.py
CHANGED
@@ -43,8 +43,9 @@ class NodeReferenceMapping:
|
|
43
43
|
fields: list[TargetFieldMapping]
|
44
44
|
|
45
45
|
@dataclass
|
46
|
-
class
|
46
|
+
class ReferencedNode:
|
47
47
|
"""Storage spec for a graph node."""
|
48
|
+
label: str
|
48
49
|
primary_key_fields: Sequence[str]
|
49
50
|
vector_indexes: Sequence[index.VectorIndexDef] = ()
|
50
51
|
|
@@ -63,10 +64,16 @@ class RelationshipMapping:
|
|
63
64
|
rel_type: str
|
64
65
|
source: NodeReferenceMapping
|
65
66
|
target: NodeReferenceMapping
|
66
|
-
nodes_storage_spec: dict[str, NodeStorageSpec] | None = None
|
67
67
|
|
68
68
|
class Neo4j(op.StorageSpec):
|
69
69
|
"""Graph storage powered by Neo4j."""
|
70
70
|
|
71
71
|
connection: AuthEntryReference
|
72
72
|
mapping: NodeMapping | RelationshipMapping
|
73
|
+
|
74
|
+
class Neo4jDeclarations(op.DeclarationSpec):
|
75
|
+
"""Declarations for Neo4j."""
|
76
|
+
|
77
|
+
kind = "Neo4j"
|
78
|
+
connection: AuthEntryReference
|
79
|
+
referenced_nodes: Sequence[ReferencedNode] = ()
|
cocoindex/tests/test_convert.py
CHANGED
@@ -1,15 +1,22 @@
|
|
1
1
|
import dataclasses
|
2
2
|
import uuid
|
3
3
|
import datetime
|
4
|
-
from dataclasses import dataclass
|
4
|
+
from dataclasses import dataclass, make_dataclass
|
5
5
|
import pytest
|
6
|
+
from cocoindex.typing import encode_enriched_type
|
6
7
|
from cocoindex.convert import to_engine_value
|
8
|
+
from cocoindex.convert import make_engine_value_converter
|
7
9
|
|
8
10
|
@dataclass
|
9
11
|
class Order:
|
10
12
|
order_id: str
|
11
13
|
name: str
|
12
14
|
price: float
|
15
|
+
extra_field: str = "default_extra"
|
16
|
+
|
17
|
+
@dataclass
|
18
|
+
class Tag:
|
19
|
+
name: str
|
13
20
|
|
14
21
|
@dataclass
|
15
22
|
class Basket:
|
@@ -19,6 +26,21 @@ class Basket:
|
|
19
26
|
class Customer:
|
20
27
|
name: str
|
21
28
|
order: Order
|
29
|
+
tags: list[Tag] = None
|
30
|
+
|
31
|
+
@dataclass
|
32
|
+
class NestedStruct:
|
33
|
+
customer: Customer
|
34
|
+
orders: list[Order]
|
35
|
+
count: int = 0
|
36
|
+
|
37
|
+
def build_engine_value_converter(engine_type_in_py, python_type=None):
|
38
|
+
"""
|
39
|
+
Helper to build a converter for the given engine-side type (as represented in Python).
|
40
|
+
If python_type is not specified, uses engine_type_in_py as the target.
|
41
|
+
"""
|
42
|
+
engine_type = encode_enriched_type(engine_type_in_py)["type"]
|
43
|
+
return make_engine_value_converter([], engine_type, python_type or engine_type_in_py)
|
22
44
|
|
23
45
|
def test_to_engine_value_basic_types():
|
24
46
|
assert to_engine_value(123) == 123
|
@@ -40,11 +62,11 @@ def test_to_engine_value_date_time_types():
|
|
40
62
|
|
41
63
|
def test_to_engine_value_struct():
|
42
64
|
order = Order(order_id="O123", name="mixed nuts", price=25.0)
|
43
|
-
assert to_engine_value(order) == ["O123", "mixed nuts", 25.0]
|
65
|
+
assert to_engine_value(order) == ["O123", "mixed nuts", 25.0, "default_extra"]
|
44
66
|
|
45
67
|
def test_to_engine_value_list_of_structs():
|
46
68
|
orders = [Order("O1", "item1", 10.0), Order("O2", "item2", 20.0)]
|
47
|
-
assert to_engine_value(orders) == [["O1", "item1", 10.0], ["O2", "item2", 20.0]]
|
69
|
+
assert to_engine_value(orders) == [["O1", "item1", 10.0, "default_extra"], ["O2", "item2", 20.0, "default_extra"]]
|
48
70
|
|
49
71
|
def test_to_engine_value_struct_with_list():
|
50
72
|
basket = Basket(items=["apple", "banana"])
|
@@ -52,7 +74,7 @@ def test_to_engine_value_struct_with_list():
|
|
52
74
|
|
53
75
|
def test_to_engine_value_nested_struct():
|
54
76
|
customer = Customer(name="Alice", order=Order("O1", "item1", 10.0))
|
55
|
-
assert to_engine_value(customer) == ["Alice", ["O1", "item1", 10.0]]
|
77
|
+
assert to_engine_value(customer) == ["Alice", ["O1", "item1", 10.0, "default_extra"], None]
|
56
78
|
|
57
79
|
def test_to_engine_value_empty_list():
|
58
80
|
assert to_engine_value([]) == []
|
@@ -67,3 +89,146 @@ def test_to_engine_value_tuple():
|
|
67
89
|
|
68
90
|
def test_to_engine_value_none():
|
69
91
|
assert to_engine_value(None) is None
|
92
|
+
|
93
|
+
def test_make_engine_value_converter_basic_types():
|
94
|
+
for engine_type_in_py, value in [
|
95
|
+
(int, 42),
|
96
|
+
(float, 3.14),
|
97
|
+
(str, "hello"),
|
98
|
+
(bool, True),
|
99
|
+
# (type(None), None), # Removed unsupported NoneType
|
100
|
+
]:
|
101
|
+
converter = build_engine_value_converter(engine_type_in_py)
|
102
|
+
assert converter(value) == value
|
103
|
+
|
104
|
+
@pytest.mark.parametrize(
|
105
|
+
"converter_type, engine_val, expected",
|
106
|
+
[
|
107
|
+
# All fields match
|
108
|
+
(Order, ["O123", "mixed nuts", 25.0, "default_extra"], Order("O123", "mixed nuts", 25.0, "default_extra")),
|
109
|
+
# Extra field in engine value (should ignore extra)
|
110
|
+
(Order, ["O123", "mixed nuts", 25.0, "default_extra", "unexpected"], Order("O123", "mixed nuts", 25.0, "default_extra")),
|
111
|
+
# Fewer fields in engine value (should fill with default)
|
112
|
+
(Order, ["O123", "mixed nuts", 0.0, "default_extra"], Order("O123", "mixed nuts", 0.0, "default_extra")),
|
113
|
+
# More fields in engine value (should ignore extra)
|
114
|
+
(Order, ["O123", "mixed nuts", 25.0, "unexpected"], Order("O123", "mixed nuts", 25.0, "unexpected")),
|
115
|
+
# Truly extra field (should ignore the fifth field)
|
116
|
+
(Order, ["O123", "mixed nuts", 25.0, "default_extra", "ignored"], Order("O123", "mixed nuts", 25.0, "default_extra")),
|
117
|
+
# Missing optional field in engine value (tags=None)
|
118
|
+
(Customer, ["Alice", ["O1", "item1", 10.0, "default_extra"], None], Customer("Alice", Order("O1", "item1", 10.0, "default_extra"), None)),
|
119
|
+
# Extra field in engine value for Customer (should ignore)
|
120
|
+
(Customer, ["Alice", ["O1", "item1", 10.0, "default_extra"], [["vip"]], "extra"], Customer("Alice", Order("O1", "item1", 10.0, "default_extra"), [Tag("vip")])),
|
121
|
+
]
|
122
|
+
)
|
123
|
+
def test_struct_conversion_cases(converter_type, engine_val, expected):
|
124
|
+
converter = build_engine_value_converter(converter_type)
|
125
|
+
assert converter(engine_val) == expected
|
126
|
+
|
127
|
+
def test_make_engine_value_converter_collections():
|
128
|
+
# List of structs
|
129
|
+
converter = build_engine_value_converter(list[Order])
|
130
|
+
engine_val = [
|
131
|
+
["O1", "item1", 10.0, "default_extra"],
|
132
|
+
["O2", "item2", 20.0, "default_extra"]
|
133
|
+
]
|
134
|
+
assert converter(engine_val) == [Order("O1", "item1", 10.0, "default_extra"), Order("O2", "item2", 20.0, "default_extra")]
|
135
|
+
# Struct with list field
|
136
|
+
converter = build_engine_value_converter(Customer)
|
137
|
+
engine_val = ["Alice", ["O1", "item1", 10.0, "default_extra"], [["vip"], ["premium"]]]
|
138
|
+
assert converter(engine_val) == Customer("Alice", Order("O1", "item1", 10.0, "default_extra"), [Tag("vip"), Tag("premium")])
|
139
|
+
# Struct with struct field
|
140
|
+
converter = build_engine_value_converter(NestedStruct)
|
141
|
+
engine_val = [
|
142
|
+
["Alice", ["O1", "item1", 10.0, "default_extra"], [["vip"]]],
|
143
|
+
[["O1", "item1", 10.0, "default_extra"], ["O2", "item2", 20.0, "default_extra"]],
|
144
|
+
2
|
145
|
+
]
|
146
|
+
assert converter(engine_val) == NestedStruct(
|
147
|
+
Customer("Alice", Order("O1", "item1", 10.0, "default_extra"), [Tag("vip")]),
|
148
|
+
[Order("O1", "item1", 10.0, "default_extra"), Order("O2", "item2", 20.0, "default_extra")],
|
149
|
+
2
|
150
|
+
)
|
151
|
+
|
152
|
+
def make_engine_order(fields):
|
153
|
+
return make_dataclass('EngineOrder', fields)
|
154
|
+
|
155
|
+
def make_python_order(fields, defaults=None):
|
156
|
+
if defaults is None:
|
157
|
+
defaults = {}
|
158
|
+
# Move all fields with defaults to the end (Python dataclass requirement)
|
159
|
+
non_default_fields = [(n, t) for n, t in fields if n not in defaults]
|
160
|
+
default_fields = [(n, t) for n, t in fields if n in defaults]
|
161
|
+
ordered_fields = non_default_fields + default_fields
|
162
|
+
# Prepare the namespace for defaults (only for fields at the end)
|
163
|
+
namespace = {k: defaults[k] for k, _ in default_fields}
|
164
|
+
return make_dataclass('PythonOrder', ordered_fields, namespace=namespace)
|
165
|
+
|
166
|
+
@pytest.mark.parametrize(
|
167
|
+
"engine_fields, python_fields, python_defaults, engine_val, expected_python_val",
|
168
|
+
[
|
169
|
+
# Extra field in Python (middle)
|
170
|
+
(
|
171
|
+
[("id", str), ("name", str)],
|
172
|
+
[("id", str), ("price", float), ("name", str)],
|
173
|
+
{"price": 0.0},
|
174
|
+
["O123", "mixed nuts"],
|
175
|
+
("O123", 0.0, "mixed nuts"),
|
176
|
+
),
|
177
|
+
# Missing field in Python (middle)
|
178
|
+
(
|
179
|
+
[("id", str), ("price", float), ("name", str)],
|
180
|
+
[("id", str), ("name", str)],
|
181
|
+
{},
|
182
|
+
["O123", 25.0, "mixed nuts"],
|
183
|
+
("O123", "mixed nuts"),
|
184
|
+
),
|
185
|
+
# Extra field in Python (start)
|
186
|
+
(
|
187
|
+
[("name", str), ("price", float)],
|
188
|
+
[("extra", str), ("name", str), ("price", float)],
|
189
|
+
{"extra": "default"},
|
190
|
+
["mixed nuts", 25.0],
|
191
|
+
("default", "mixed nuts", 25.0),
|
192
|
+
),
|
193
|
+
# Missing field in Python (start)
|
194
|
+
(
|
195
|
+
[("extra", str), ("name", str), ("price", float)],
|
196
|
+
[("name", str), ("price", float)],
|
197
|
+
{},
|
198
|
+
["unexpected", "mixed nuts", 25.0],
|
199
|
+
("mixed nuts", 25.0),
|
200
|
+
),
|
201
|
+
# Field order difference (should map by name)
|
202
|
+
(
|
203
|
+
[("id", str), ("name", str), ("price", float)],
|
204
|
+
[("name", str), ("id", str), ("price", float), ("extra", str)],
|
205
|
+
{"extra": "default"},
|
206
|
+
["O123", "mixed nuts", 25.0],
|
207
|
+
("mixed nuts", "O123", 25.0, "default"),
|
208
|
+
),
|
209
|
+
# Extra field (Python has extra field with default)
|
210
|
+
(
|
211
|
+
[("id", str), ("name", str)],
|
212
|
+
[("id", str), ("name", str), ("price", float)],
|
213
|
+
{"price": 0.0},
|
214
|
+
["O123", "mixed nuts"],
|
215
|
+
("O123", "mixed nuts", 0.0),
|
216
|
+
),
|
217
|
+
# Missing field (Engine has extra field)
|
218
|
+
(
|
219
|
+
[("id", str), ("name", str), ("price", float)],
|
220
|
+
[("id", str), ("name", str)],
|
221
|
+
{},
|
222
|
+
["O123", "mixed nuts", 25.0],
|
223
|
+
("O123", "mixed nuts"),
|
224
|
+
),
|
225
|
+
]
|
226
|
+
)
|
227
|
+
def test_field_position_cases(engine_fields, python_fields, python_defaults, engine_val, expected_python_val):
|
228
|
+
EngineOrder = make_engine_order(engine_fields)
|
229
|
+
PythonOrder = make_python_order(python_fields, python_defaults)
|
230
|
+
converter = build_engine_value_converter(EngineOrder, PythonOrder)
|
231
|
+
# Map field names to expected values
|
232
|
+
expected_dict = dict(zip([f[0] for f in python_fields], expected_python_val))
|
233
|
+
# Instantiate using keyword arguments (order doesn't matter)
|
234
|
+
assert converter(engine_val) == PythonOrder(**expected_dict)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: cocoindex
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.26
|
4
4
|
Requires-Dist: sentence-transformers>=3.3.1
|
5
5
|
Requires-Dist: click>=8.1.8
|
6
6
|
Requires-Dist: pytest ; extra == 'test'
|
@@ -117,7 +117,7 @@ Go to the [examples directory](examples) to try out with any of the examples, fo
|
|
117
117
|
| [PDF Embedding](examples/pdf_embedding) | Parse PDF and index text embeddings for semantic search |
|
118
118
|
| [Manuals LLM Extraction](examples/manuals_llm_extraction) | Extract structured information from a manual using LLM |
|
119
119
|
| [Google Drive Text Embedding](examples/gdrive_text_embedding) | Index text documents from Google Drive |
|
120
|
-
| [Docs to Knowledge Graph](examples/
|
120
|
+
| [Docs to Knowledge Graph](examples/docs_to_knowledge_graph) | Extract relationships from Markdown documents and build a knowledge graph |
|
121
121
|
| [Embeddings to Qdrant](examples/text_embedding_qdrant) | Index documents in a Qdrant collection for semantic search |
|
122
122
|
|
123
123
|
More coming and stay tuned! If there's any specific examples you would like to see, please let us know in our [Discord community](https://discord.com/invite/zpA9S2DR7s) 🌱.
|
@@ -0,0 +1,24 @@
|
|
1
|
+
cocoindex-0.1.26.dist-info/METADATA,sha256=BwpB45xAUpYFZugav3l9u1uJ_0s4Kdrnp-Y2FnSJZ0A,8079
|
2
|
+
cocoindex-0.1.26.dist-info/WHEEL,sha256=wsVBlw9xyAuHecZeOYqJ_tA7emUKfXYOn-_180uZRi4,104
|
3
|
+
cocoindex-0.1.26.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
4
|
+
cocoindex/functions.py,sha256=xcAeRQTy9JObfxpjyMn-dPY2y7XhVWjB7759xVyup6o,1657
|
5
|
+
cocoindex/query.py,sha256=XsVY5cBJJ3a70qazkcCHjWZLE1zBqzMQ4HVSulicGMA,3273
|
6
|
+
cocoindex/index.py,sha256=LssEOuZi6AqhwKtZM3QFeQpa9T-0ELi8G5DsrYKECvc,534
|
7
|
+
cocoindex/lib.py,sha256=48nfWSg5IMzTSkVxdrWF8d9Hi-Bw8in_2rs7rQRwAs8,3505
|
8
|
+
cocoindex/auth_registry.py,sha256=lZ2rD5_9aC_UpGk7t4TmSYal_rjN7eHgO4_sU7FR0Zw,620
|
9
|
+
cocoindex/convert.py,sha256=tzlHadc-SaZCRBWxZEp08T4clJQPab_eXt6mUub0iQQ,5017
|
10
|
+
cocoindex/tests/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
11
|
+
cocoindex/tests/test_convert.py,sha256=mT_7HhXRu1PzGFbdmPmrq3O5-bPyDDG7mbr-mHaa2i0,9339
|
12
|
+
cocoindex/__init__.py,sha256=f4LTPg4db7Wm3QO9HirvhsT11OVykiFxGbt1JK6taFA,572
|
13
|
+
cocoindex/flow.py,sha256=1Mx-rYBzPIlFLNsiNVGhPtJKy2u6stZT1xjyPzERbFI,21068
|
14
|
+
cocoindex/llm.py,sha256=4b20wpSHcgfDM7tdxRm1KIo_7C30nT7h0gCsWvs686I,320
|
15
|
+
cocoindex/runtime.py,sha256=jqRnWkkIlAhE04gi4y0Y5bzuq9FX4j0aVNU-nengLJk,980
|
16
|
+
cocoindex/op.py,sha256=zOQzgnVDvET8LtUt7TW8PfHMhaA0eAXoZ3c_EsL6jtU,10602
|
17
|
+
cocoindex/sources.py,sha256=wZFU8lwSXjyofJR-syySH9fTyPnBlAPJ6-1hQNX8fGA,936
|
18
|
+
cocoindex/setup.py,sha256=W1HshwYk_K2aeLOVn_e62ZOXBO9yWsoUboRiH4SjF48,496
|
19
|
+
cocoindex/cli.py,sha256=MvEUbQVrJy-sYbGQNsqIaMJvcQXQn1OQVNues22Hph0,7061
|
20
|
+
cocoindex/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
21
|
+
cocoindex/typing.py,sha256=4mP9VXS75s3VMfF1LDc1LXsBng7uGdR5aD73N8iaeSM,7282
|
22
|
+
cocoindex/storages.py,sha256=GRHkmwuSAU7neF3H0pjAPfeEkmtXv-DJc7CKYjcATvE,1946
|
23
|
+
cocoindex/_engine.cpython-311-darwin.so,sha256=dE-o616jWjBbhjVm5UV-OzqI24h-IDitUp9io_ys29s,59305536
|
24
|
+
cocoindex-0.1.26.dist-info/RECORD,,
|
@@ -1,24 +0,0 @@
|
|
1
|
-
cocoindex-0.1.25.dist-info/METADATA,sha256=PX5VOu90kauphEVYmZz8mKL5zzC_9pkdpLCCTU-0RmE,8066
|
2
|
-
cocoindex-0.1.25.dist-info/WHEEL,sha256=wsVBlw9xyAuHecZeOYqJ_tA7emUKfXYOn-_180uZRi4,104
|
3
|
-
cocoindex-0.1.25.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
4
|
-
cocoindex/functions.py,sha256=xcAeRQTy9JObfxpjyMn-dPY2y7XhVWjB7759xVyup6o,1657
|
5
|
-
cocoindex/query.py,sha256=XsVY5cBJJ3a70qazkcCHjWZLE1zBqzMQ4HVSulicGMA,3273
|
6
|
-
cocoindex/index.py,sha256=LssEOuZi6AqhwKtZM3QFeQpa9T-0ELi8G5DsrYKECvc,534
|
7
|
-
cocoindex/lib.py,sha256=GwSsbUCTTzikQRQSIRCDEpgYk6FBjbjmsy3NtQpeOl0,3461
|
8
|
-
cocoindex/auth_registry.py,sha256=lZ2rD5_9aC_UpGk7t4TmSYal_rjN7eHgO4_sU7FR0Zw,620
|
9
|
-
cocoindex/convert.py,sha256=tzlHadc-SaZCRBWxZEp08T4clJQPab_eXt6mUub0iQQ,5017
|
10
|
-
cocoindex/tests/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
11
|
-
cocoindex/tests/test_convert.py,sha256=wSAiskmBgBM5UZussbNXmCxt47hHGlSEEQVgDZaY4TE,2058
|
12
|
-
cocoindex/__init__.py,sha256=f4LTPg4db7Wm3QO9HirvhsT11OVykiFxGbt1JK6taFA,572
|
13
|
-
cocoindex/flow.py,sha256=pxLUWayN1hIJvxlrPJmJToUwGZAuV9it7DzhURGqDco,19973
|
14
|
-
cocoindex/llm.py,sha256=uHXub9AWTOtxNyTaefHY-VuY_yzo6ikM1kUxHsQn-zw,298
|
15
|
-
cocoindex/runtime.py,sha256=cByhaMWiOsejdsVa2WQ-79hlKLKqlDGJtcsDivKGGfY,692
|
16
|
-
cocoindex/op.py,sha256=UXaurZrykiViu0p05wjzDuPvK0q6_Mu71zbcuYu2U8s,10343
|
17
|
-
cocoindex/sources.py,sha256=wZFU8lwSXjyofJR-syySH9fTyPnBlAPJ6-1hQNX8fGA,936
|
18
|
-
cocoindex/setup.py,sha256=W1HshwYk_K2aeLOVn_e62ZOXBO9yWsoUboRiH4SjF48,496
|
19
|
-
cocoindex/cli.py,sha256=jRwGCPuqkBz_cIp7LKARCRAiDsPm91bsduKzfYJohJ4,6931
|
20
|
-
cocoindex/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
21
|
-
cocoindex/typing.py,sha256=4mP9VXS75s3VMfF1LDc1LXsBng7uGdR5aD73N8iaeSM,7282
|
22
|
-
cocoindex/storages.py,sha256=fj8YJPEYSVmHc5DLQ-UpR0lfxFXD-rUBQF3RVlb1NcY,1810
|
23
|
-
cocoindex/_engine.cpython-311-darwin.so,sha256=RaGHeVL1ILG-B5qwijOH4EJNYQLv3Wp2-zxPr2eBHyU,59012720
|
24
|
-
cocoindex-0.1.25.dist-info/RECORD,,
|
File without changes
|
File without changes
|