cocoindex 0.2.10__cp311-abi3-win_amd64.whl → 0.2.11__cp311-abi3-win_amd64.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 CHANGED
@@ -24,6 +24,7 @@ from .llm import LlmSpec, LlmApiType
24
24
  from .index import VectorSimilarityMetric, VectorIndexDef, IndexOptions
25
25
  from .setting import DatabaseConnectionSpec, Settings, ServerSettings
26
26
  from .setting import get_app_namespace
27
+ from .query_handler import QueryHandlerResultFields, QueryInfo, QueryOutput
27
28
  from .typing import (
28
29
  Int64,
29
30
  Float32,
@@ -95,4 +96,8 @@ __all__ = [
95
96
  "Range",
96
97
  "Vector",
97
98
  "Json",
99
+ # Query handler
100
+ "QueryHandlerResultFields",
101
+ "QueryInfo",
102
+ "QueryOutput",
98
103
  ]
cocoindex/_engine.pyd CHANGED
Binary file
@@ -11,18 +11,6 @@ from .convert import dump_engine_object
11
11
 
12
12
  T = TypeVar("T")
13
13
 
14
- # Global atomic counter for generating unique auth entry keys
15
- _counter_lock = threading.Lock()
16
- _auth_key_counter = 0
17
-
18
-
19
- def _generate_auth_key() -> str:
20
- """Generate a unique auth entry key using a global atomic counter."""
21
- global _auth_key_counter # pylint: disable=global-statement
22
- with _counter_lock:
23
- _auth_key_counter += 1
24
- return f"__auth_{_auth_key_counter}"
25
-
26
14
 
27
15
  @dataclass
28
16
  class TransientAuthEntryReference(Generic[T]):
@@ -37,7 +25,8 @@ class AuthEntryReference(TransientAuthEntryReference[T]):
37
25
 
38
26
  def add_transient_auth_entry(value: T) -> TransientAuthEntryReference[T]:
39
27
  """Add an auth entry to the registry. Returns its reference."""
40
- return add_auth_entry(_generate_auth_key(), value)
28
+ key = _engine.add_transient_auth_entry(dump_engine_object(value))
29
+ return TransientAuthEntryReference(key)
41
30
 
42
31
 
43
32
  def add_auth_entry(key: str, value: T) -> AuthEntryReference[T]:
cocoindex/convert.py CHANGED
@@ -616,6 +616,8 @@ def dump_engine_object(v: Any) -> Any:
616
616
  return s
617
617
  elif isinstance(v, (list, tuple)):
618
618
  return [dump_engine_object(item) for item in v]
619
+ elif isinstance(v, np.ndarray):
620
+ return v.tolist()
619
621
  elif isinstance(v, dict):
620
622
  return {k: dump_engine_object(v) for k, v in v.items()}
621
623
  return v
cocoindex/flow.py CHANGED
@@ -38,9 +38,10 @@ from .convert import (
38
38
  make_engine_value_encoder,
39
39
  )
40
40
  from .op import FunctionSpec
41
- from .runtime import execution_context
41
+ from .runtime import execution_context, to_async_call
42
42
  from .setup import SetupChangeBundle
43
43
  from .typing import analyze_type_info, encode_enriched_type
44
+ from .query_handler import QueryHandlerInfo, QueryHandlerResultFields
44
45
  from .validation import (
45
46
  validate_flow_name,
46
47
  validate_full_flow_name,
@@ -694,23 +695,18 @@ class Flow:
694
695
  """
695
696
 
696
697
  _name: str
697
- _lazy_engine_flow: Callable[[], _engine.Flow] | None
698
+ _engine_flow_creator: Callable[[], _engine.Flow]
699
+
700
+ _lazy_flow_lock: Lock
701
+ _lazy_query_handler_args: list[tuple[Any, ...]]
702
+ _lazy_engine_flow: _engine.Flow | None = None
698
703
 
699
704
  def __init__(self, name: str, engine_flow_creator: Callable[[], _engine.Flow]):
700
705
  validate_flow_name(name)
701
706
  self._name = name
702
- engine_flow = None
703
- lock = Lock()
704
-
705
- def _lazy_engine_flow() -> _engine.Flow:
706
- nonlocal engine_flow, lock
707
- if engine_flow is None:
708
- with lock:
709
- if engine_flow is None:
710
- engine_flow = engine_flow_creator()
711
- return engine_flow
712
-
713
- self._lazy_engine_flow = _lazy_engine_flow
707
+ self._engine_flow_creator = engine_flow_creator
708
+ self._lazy_flow_lock = Lock()
709
+ self._lazy_query_handler_args = []
714
710
 
715
711
  def _render_spec(self, verbose: bool = False) -> Tree:
716
712
  """
@@ -794,15 +790,33 @@ class Flow:
794
790
  """
795
791
  Get the engine flow.
796
792
  """
797
- if self._lazy_engine_flow is None:
798
- raise RuntimeError(f"Flow {self.full_name} is already removed")
799
- return self._lazy_engine_flow()
793
+ if self._lazy_engine_flow is not None:
794
+ return self._lazy_engine_flow
795
+ return self._internal_flow()
800
796
 
801
797
  async def internal_flow_async(self) -> _engine.Flow:
802
798
  """
803
799
  Get the engine flow. The async version.
804
800
  """
805
- return await asyncio.to_thread(self.internal_flow)
801
+ if self._lazy_engine_flow is not None:
802
+ return self._lazy_engine_flow
803
+ return await asyncio.to_thread(self._internal_flow)
804
+
805
+ def _internal_flow(self) -> _engine.Flow:
806
+ """
807
+ Get the engine flow. The async version.
808
+ """
809
+ with self._lazy_flow_lock:
810
+ if self._lazy_engine_flow is not None:
811
+ return self._lazy_engine_flow
812
+
813
+ engine_flow = self._engine_flow_creator()
814
+ self._lazy_engine_flow = engine_flow
815
+ for args in self._lazy_query_handler_args:
816
+ engine_flow.add_query_handler(*args)
817
+ self._lazy_query_handler_args = []
818
+
819
+ return engine_flow
806
820
 
807
821
  def setup(self, report_to_stdout: bool = False) -> None:
808
822
  """
@@ -847,6 +861,53 @@ class Flow:
847
861
  with _flows_lock:
848
862
  del _flows[self.name]
849
863
 
864
+ def add_query_handler(
865
+ self,
866
+ name: str,
867
+ handler: Callable[[str], Any],
868
+ /,
869
+ *,
870
+ result_fields: QueryHandlerResultFields | None = None,
871
+ ) -> None:
872
+ """
873
+ Add a query handler to the flow.
874
+ """
875
+ async_handler = to_async_call(handler)
876
+
877
+ async def _handler(query: str) -> dict[str, Any]:
878
+ handler_result = await async_handler(query)
879
+ return {
880
+ "results": [
881
+ [(k, dump_engine_object(v)) for (k, v) in result.items()]
882
+ for result in handler_result.results
883
+ ],
884
+ "query_info": dump_engine_object(handler_result.query_info),
885
+ }
886
+
887
+ handler_info = dump_engine_object(QueryHandlerInfo(result_fields=result_fields))
888
+ with self._lazy_flow_lock:
889
+ if self._lazy_engine_flow is not None:
890
+ self._lazy_engine_flow.add_query_handler(name, _handler, handler_info)
891
+ else:
892
+ self._lazy_query_handler_args.append((name, _handler, handler_info))
893
+
894
+ def query_handler(
895
+ self,
896
+ name: str | None = None,
897
+ result_fields: QueryHandlerResultFields | None = None,
898
+ ) -> Callable[[Callable[[str], Any]], Callable[[str], Any]]:
899
+ """
900
+ A decorator to declare a query handler.
901
+ """
902
+
903
+ def _inner(handler: Callable[[str], Any]) -> Callable[[str], Any]:
904
+ self.add_query_handler(
905
+ name or handler.__qualname__, handler, result_fields=result_fields
906
+ )
907
+ return handler
908
+
909
+ return _inner
910
+
850
911
 
851
912
  def _create_lazy_flow(
852
913
  name: str | None, fl_def: Callable[[FlowBuilder, DataScope], None]
cocoindex/op.py CHANGED
@@ -2,7 +2,6 @@
2
2
  Facilities for defining cocoindex operations.
3
3
  """
4
4
 
5
- import asyncio
6
5
  import dataclasses
7
6
  import inspect
8
7
  from enum import Enum
@@ -32,6 +31,7 @@ from .typing import (
32
31
  AnalyzedAnyType,
33
32
  AnalyzedDictType,
34
33
  )
34
+ from .runtime import to_async_call
35
35
 
36
36
 
37
37
  class OpCategory(Enum):
@@ -150,12 +150,6 @@ class OpArgs:
150
150
  arg_relationship: tuple[ArgRelationship, str] | None = None
151
151
 
152
152
 
153
- def _to_async_call(call: Callable[..., Any]) -> Callable[..., Awaitable[Any]]:
154
- if inspect.iscoroutinefunction(call):
155
- return call
156
- return lambda *args, **kwargs: asyncio.to_thread(lambda: call(*args, **kwargs))
157
-
158
-
159
153
  @dataclasses.dataclass
160
154
  class _ArgInfo:
161
155
  decoder: Callable[[Any], Any]
@@ -319,8 +313,8 @@ def _register_op_factory(
319
313
  """
320
314
  prepare_method = getattr(self._executor, "prepare", None)
321
315
  if prepare_method is not None:
322
- await _to_async_call(prepare_method)()
323
- self._acall = _to_async_call(self._executor.__call__)
316
+ await to_async_call(prepare_method)()
317
+ self._acall = to_async_call(self._executor.__call__)
324
318
 
325
319
  async def __call__(self, *args: Any, **kwargs: Any) -> Any:
326
320
  decoded_args = []
@@ -461,12 +455,12 @@ class _TargetConnector:
461
455
  self._get_persistent_key_fn = _get_required_method(
462
456
  connector_cls, "get_persistent_key"
463
457
  )
464
- self._apply_setup_change_async_fn = _to_async_call(
458
+ self._apply_setup_change_async_fn = to_async_call(
465
459
  _get_required_method(connector_cls, "apply_setup_change")
466
460
  )
467
461
 
468
462
  mutate_fn = _get_required_method(connector_cls, "mutate")
469
- self._mutate_async_fn = _to_async_call(mutate_fn)
463
+ self._mutate_async_fn = to_async_call(mutate_fn)
470
464
 
471
465
  # Store the type annotation for later use
472
466
  self._mutatation_type = self._analyze_mutate_mutation_type(
@@ -0,0 +1,51 @@
1
+ import dataclasses
2
+ import numpy as np
3
+ from numpy import typing as npt
4
+ from typing import Generic, TypeVar
5
+ from .index import VectorSimilarityMetric
6
+
7
+
8
+ @dataclasses.dataclass
9
+ class QueryHandlerResultFields:
10
+ """
11
+ Specify field names in query results returned by the query handler.
12
+ This provides metadata for tools like CocoInsight to recognize structure of the query results.
13
+ """
14
+
15
+ embedding: list[str] = dataclasses.field(default_factory=list)
16
+ score: str | None = None
17
+
18
+
19
+ @dataclasses.dataclass
20
+ class QueryHandlerInfo:
21
+ """
22
+ Info to configure a query handler.
23
+ """
24
+
25
+ result_fields: QueryHandlerResultFields | None = None
26
+
27
+
28
+ @dataclasses.dataclass
29
+ class QueryInfo:
30
+ """
31
+ Info about the query.
32
+ """
33
+
34
+ embedding: list[float] | npt.NDArray[np.float32] | None = None
35
+ similarity_metric: VectorSimilarityMetric | None = None
36
+
37
+
38
+ R = TypeVar("R")
39
+
40
+
41
+ @dataclasses.dataclass
42
+ class QueryOutput(Generic[R]):
43
+ """
44
+ Output of a query handler.
45
+
46
+ results: list of results. Each result can be a dict or a dataclass.
47
+ query_info: Info about the query.
48
+ """
49
+
50
+ results: list[R]
51
+ query_info: QueryInfo = dataclasses.field(default_factory=QueryInfo)
cocoindex/runtime.py CHANGED
@@ -5,7 +5,8 @@ manner.
5
5
 
6
6
  import threading
7
7
  import asyncio
8
- from typing import Any, Coroutine, TypeVar
8
+ import inspect
9
+ from typing import Any, Callable, Coroutine, TypeVar, Awaitable
9
10
 
10
11
 
11
12
  T = TypeVar("T")
@@ -35,3 +36,9 @@ class _ExecutionContext:
35
36
 
36
37
 
37
38
  execution_context = _ExecutionContext()
39
+
40
+
41
+ def to_async_call(call: Callable[..., Any]) -> Callable[..., Awaitable[Any]]:
42
+ if inspect.iscoroutinefunction(call):
43
+ return call
44
+ return lambda *args, **kwargs: asyncio.to_thread(lambda: call(*args, **kwargs))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cocoindex
3
- Version: 0.2.10
3
+ Version: 0.2.11
4
4
  Classifier: Development Status :: 3 - Alpha
5
5
  Classifier: License :: OSI Approved :: Apache Software License
6
6
  Classifier: Operating System :: OS Independent
@@ -1,20 +1,21 @@
1
- cocoindex-0.2.10.dist-info/METADATA,sha256=KQcBWfVM6cEl93NcnSCeaPZx3pQSmkQr030zQZ2vvAw,13215
2
- cocoindex-0.2.10.dist-info/WHEEL,sha256=8hEf8NzM1FnmM77AjVt5h8nDuYkN3UqZ79LoIAHXeRE,95
3
- cocoindex-0.2.10.dist-info/entry_points.txt,sha256=_NretjYVzBdNTn7dK-zgwr7YfG2afz1u1uSE-5bZXF8,46
4
- cocoindex-0.2.10.dist-info/licenses/THIRD_PARTY_NOTICES.html,sha256=s4PFFZPP5i9n3Gw4VuPYKYYU5pc2bK9W9ipjRkQoygw,717804
5
- cocoindex/__init__.py,sha256=5QfLfEQ3NpWzhLCbOCLFcvNd-tvCgp_d9_MZjksSFqE,2362
6
- cocoindex/_engine.pyd,sha256=PuMNCJ6Oax64wTR7hDa4bP_3RxgsRSxQrdSDinbyaxk,72942592
7
- cocoindex/auth_registry.py,sha256=Qq1IVZb-7K4luRrQSDlOPbISnGEZ4kIDsrCU8H2ARw0,1529
1
+ cocoindex-0.2.11.dist-info/METADATA,sha256=azjBskKzo4W0_RuO8XyolrdX9B7AAv30ryyfVn2kZ4c,13215
2
+ cocoindex-0.2.11.dist-info/WHEEL,sha256=8hEf8NzM1FnmM77AjVt5h8nDuYkN3UqZ79LoIAHXeRE,95
3
+ cocoindex-0.2.11.dist-info/entry_points.txt,sha256=_NretjYVzBdNTn7dK-zgwr7YfG2afz1u1uSE-5bZXF8,46
4
+ cocoindex-0.2.11.dist-info/licenses/THIRD_PARTY_NOTICES.html,sha256=o8g8gIN-zaK02_7ZMPlqompannwA2SwEB0e30CKRN1E,717804
5
+ cocoindex/__init__.py,sha256=7FXm4Q_I_aLgVxY2nd5PTWQ6Z92mTjJ1B5WxTHpj6p8,2531
6
+ cocoindex/_engine.pyd,sha256=lVSYJ3dZJ5ouqBsel_g3qXP5gzcBHSLD5kWujb5AlFs,73029632
7
+ cocoindex/auth_registry.py,sha256=vfVen6nm_sVMoUdXKf4ucrO-wOudgculrxJ2Ob94Lwo,1185
8
8
  cocoindex/cli.py,sha256=Wt8wUjWqHEBoR3gDnHqIplLaL_tbStMCHAOuYhYq84A,22338
9
- cocoindex/convert.py,sha256=iKVufQXjsrvS8psKKZZYxuLohJvBIVctgKaRLLt8jK0,22711
10
- cocoindex/flow.py,sha256=w6FimPK4Nll9nYccbX-vjDMXzYb2lV66GmgikAMR5UQ,38802
9
+ cocoindex/convert.py,sha256=AhF33zfRFeqnRb8fiEzmhD7S4EWJHyeXW0RrB6_KNPw,22775
10
+ cocoindex/flow.py,sha256=TD-9w2uB-hvOVrs8CTE2lqKKzewKRT2qbJ3SAYhiAxE,41039
11
11
  cocoindex/functions.py,sha256=CtiwTVW6g4BtO5_EvVcij7Si4Bx-axnM1hsdU43aM4g,12617
12
12
  cocoindex/index.py,sha256=GrqTm1rLwICQ8hadtNvJAxVg7GWMvtMmFcbiNtNzmP0,569
13
13
  cocoindex/lib.py,sha256=-Ys4a7BdDOW_ctz9vEBA5fSOqwsY_IKLHxaDwxvoXRg,2359
14
14
  cocoindex/llm.py,sha256=JM5EPup7FZojynCI0rg4gnMBYmdPZpaHJbVBz3f7BAI,897
15
- cocoindex/op.py,sha256=yO8QdfauUEe-3n1xZmhHhyr0EHqYEokcJRxtd3OtMo8,22255
15
+ cocoindex/op.py,sha256=6y7UHk0mThJVfYzPfyefjxKTAXIWoUp6pvYqNUo6HLI,22038
16
16
  cocoindex/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- cocoindex/runtime.py,sha256=6mE-jR1Kh5c4GETDvBgwjXZq69TK5rh1qNpaseRDZnw,1117
17
+ cocoindex/query_handler.py,sha256=iC4_j0yiQrW1izHDJUgaQyNrTpyfTvYIOwoshYNclKk,1243
18
+ cocoindex/runtime.py,sha256=ZjyPmohIWGFDRaOdS0MgeVuf4Ra2Ggbp6xraxwvHFBo,1386
18
19
  cocoindex/setting.py,sha256=oohs7H52_h1AulDcp96cS9SVAAm_uFaQyJYog9EvCj0,5131
19
20
  cocoindex/setup.py,sha256=KbJvmeFu0NbeoH-5iDmHZP86f26HIId8kHmGUNZAePI,3160
20
21
  cocoindex/sources.py,sha256=Ij8LyYbM49zjPg-pJHMbas8sdLquEcgvmuYfv7wCdjI,3290
@@ -30,4 +31,4 @@ cocoindex/typing.py,sha256=bkKcp4G_xQODwQn92sfeNi-kXSWnbtlwTpFbrMGCnj4,14673
30
31
  cocoindex/user_app_loader.py,sha256=ZkvUG9aJNNECAjwTY0ZYtNpFd9dNBPVoPKGTtB7dSZg,1926
31
32
  cocoindex/utils.py,sha256=U3W39zD2uZpXX8v84tJD7sRmbC5ar3z_ljAP1cJrYXI,618
32
33
  cocoindex/validation.py,sha256=4ZjsW-SZT8X_TEEhEE6QG6D-8Oq_TkPAhTqP0mdFYSE,3194
33
- cocoindex-0.2.10.dist-info/RECORD,,
34
+ cocoindex-0.2.11.dist-info/RECORD,,
@@ -2428,7 +2428,7 @@ Software.
2428
2428
  <h3 id="Apache-2.0">Apache License 2.0</h3>
2429
2429
  <h4>Used by:</h4>
2430
2430
  <ul class="license-used-by">
2431
- <li><a href=" https://crates.io/crates/cocoindex ">cocoindex 0.2.10</a></li>
2431
+ <li><a href=" https://crates.io/crates/cocoindex ">cocoindex 0.2.11</a></li>
2432
2432
  <li><a href=" https://github.com/awesomized/crc-fast-rust ">crc-fast 1.3.0</a></li>
2433
2433
  <li><a href=" https://github.com/qdrant/rust-client ">qdrant-client 1.15.0</a></li>
2434
2434
  </ul>