cocoindex 0.1.65__pp311-pypy311_pp73-manylinux_2_28_aarch64.whl → 0.1.66__pp311-pypy311_pp73-manylinux_2_28_aarch64.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
@@ -33,6 +33,7 @@ __all__ = [
33
33
  # Submodules
34
34
  "_engine",
35
35
  "functions",
36
+ "llm",
36
37
  "sources",
37
38
  "targets",
38
39
  "storages",
cocoindex/convert.py CHANGED
@@ -89,13 +89,26 @@ def make_engine_value_decoder(
89
89
  if dst_is_any:
90
90
  if src_type_kind == "Union":
91
91
  return lambda value: value[1]
92
- if src_type_kind == "Struct" or src_type_kind in TABLE_TYPES:
92
+ if src_type_kind == "Struct":
93
+ return _make_engine_struct_to_dict_decoder(field_path, src_type["fields"])
94
+ if src_type_kind in TABLE_TYPES:
93
95
  raise ValueError(
94
96
  f"Missing type annotation for `{''.join(field_path)}`."
95
97
  f"It's required for {src_type_kind} type."
96
98
  )
97
99
  return lambda value: value
98
100
 
101
+ # Handle struct -> dict binding for explicit dict annotations
102
+ is_dict_annotation = False
103
+ if dst_annotation is dict:
104
+ is_dict_annotation = True
105
+ elif getattr(dst_annotation, "__origin__", None) is dict:
106
+ args = getattr(dst_annotation, "__args__", ())
107
+ if args == (str, Any):
108
+ is_dict_annotation = True
109
+ if is_dict_annotation and src_type_kind == "Struct":
110
+ return _make_engine_struct_to_dict_decoder(field_path, src_type["fields"])
111
+
99
112
  dst_type_info = analyze_type_info(dst_annotation)
100
113
 
101
114
  if src_type_kind == "Union":
@@ -294,6 +307,39 @@ def _make_engine_struct_value_decoder(
294
307
  )
295
308
 
296
309
 
310
+ def _make_engine_struct_to_dict_decoder(
311
+ field_path: list[str],
312
+ src_fields: list[dict[str, Any]],
313
+ ) -> Callable[[list[Any] | None], dict[str, Any] | None]:
314
+ """Make a decoder from engine field values to a Python dict."""
315
+
316
+ field_decoders = []
317
+ for i, field_schema in enumerate(src_fields):
318
+ field_name = field_schema["name"]
319
+ field_path.append(f".{field_name}")
320
+ field_decoder = make_engine_value_decoder(
321
+ field_path,
322
+ field_schema["type"],
323
+ Any, # Use Any for recursive decoding
324
+ )
325
+ field_path.pop()
326
+ field_decoders.append((field_name, field_decoder))
327
+
328
+ def decode_to_dict(values: list[Any] | None) -> dict[str, Any] | None:
329
+ if values is None:
330
+ return None
331
+ if len(field_decoders) != len(values):
332
+ raise ValueError(
333
+ f"Field count mismatch: expected {len(field_decoders)}, got {len(values)}"
334
+ )
335
+ return {
336
+ field_name: field_decoder(value)
337
+ for value, (field_name, field_decoder) in zip(values, field_decoders)
338
+ }
339
+
340
+ return decode_to_dict
341
+
342
+
297
343
  def dump_engine_object(v: Any) -> Any:
298
344
  """Recursively dump an object for engine. Engine side uses `Pythonized` to catch."""
299
345
  if v is None:
cocoindex/functions.py CHANGED
@@ -45,6 +45,7 @@ class EmbedText(op.FunctionSpec):
45
45
  address: str | None = None
46
46
  output_dimension: int | None = None
47
47
  task_type: str | None = None
48
+ api_config: llm.VertexAiConfig | None = None
48
49
 
49
50
 
50
51
  class ExtractByLlm(op.FunctionSpec):
cocoindex/llm.py CHANGED
@@ -8,6 +8,7 @@ class LlmApiType(Enum):
8
8
  OPENAI = "OpenAi"
9
9
  OLLAMA = "Ollama"
10
10
  GEMINI = "Gemini"
11
+ VERTEX_AI = "VertexAi"
11
12
  ANTHROPIC = "Anthropic"
12
13
  LITE_LLM = "LiteLlm"
13
14
  OPEN_ROUTER = "OpenRouter"
@@ -15,6 +16,16 @@ class LlmApiType(Enum):
15
16
  VLLM = "Vllm"
16
17
 
17
18
 
19
+ @dataclass
20
+ class VertexAiConfig:
21
+ """A specification for a Vertex AI LLM."""
22
+
23
+ kind = "VertexAi"
24
+
25
+ project: str
26
+ region: str | None = None
27
+
28
+
18
29
  @dataclass
19
30
  class LlmSpec:
20
31
  """A specification for a LLM."""
@@ -22,3 +33,4 @@ class LlmSpec:
22
33
  api_type: LlmApiType
23
34
  model: str
24
35
  address: str | None = None
36
+ api_config: VertexAiConfig | None = None
cocoindex/sources.py CHANGED
@@ -43,3 +43,18 @@ class AmazonS3(op.SourceSpec):
43
43
  included_patterns: list[str] | None = None
44
44
  excluded_patterns: list[str] | None = None
45
45
  sqs_queue_url: str | None = None
46
+
47
+
48
+ class AzureBlob(op.SourceSpec):
49
+ """
50
+ Import data from an Azure Blob Storage container. Supports optional prefix and file filtering by glob patterns.
51
+ """
52
+
53
+ _op_category = op.OpCategory.SOURCE
54
+
55
+ account_name: str
56
+ container_name: str
57
+ prefix: str | None = None
58
+ binary: bool = False
59
+ included_patterns: list[str] | None = None
60
+ excluded_patterns: list[str] | None = None
@@ -1229,3 +1229,115 @@ def test_full_roundtrip_scalar_with_python_types() -> None:
1229
1229
  annotated_float=2.0,
1230
1230
  )
1231
1231
  validate_full_roundtrip(instance, MixedStruct)
1232
+
1233
+
1234
+ def test_roundtrip_struct_to_dict_binding() -> None:
1235
+ """Test struct -> dict binding with Any annotation."""
1236
+
1237
+ @dataclass
1238
+ class SimpleStruct:
1239
+ name: str
1240
+ value: int
1241
+ price: float
1242
+
1243
+ instance = SimpleStruct("test", 42, 3.14)
1244
+ expected_dict = {"name": "test", "value": 42, "price": 3.14}
1245
+
1246
+ # Test Any annotation
1247
+ validate_full_roundtrip(instance, SimpleStruct, (expected_dict, Any))
1248
+
1249
+
1250
+ def test_roundtrip_struct_to_dict_explicit() -> None:
1251
+ """Test struct -> dict binding with explicit dict annotations."""
1252
+
1253
+ @dataclass
1254
+ class Product:
1255
+ id: str
1256
+ name: str
1257
+ price: float
1258
+ active: bool
1259
+
1260
+ instance = Product("P1", "Widget", 29.99, True)
1261
+ expected_dict = {"id": "P1", "name": "Widget", "price": 29.99, "active": True}
1262
+
1263
+ # Test explicit dict annotations
1264
+ validate_full_roundtrip(
1265
+ instance, Product, (expected_dict, dict), (expected_dict, dict[str, Any])
1266
+ )
1267
+
1268
+
1269
+ def test_roundtrip_struct_to_dict_with_none_annotation() -> None:
1270
+ """Test struct -> dict binding with None annotation."""
1271
+
1272
+ @dataclass
1273
+ class Config:
1274
+ host: str
1275
+ port: int
1276
+ debug: bool
1277
+
1278
+ instance = Config("localhost", 8080, True)
1279
+ expected_dict = {"host": "localhost", "port": 8080, "debug": True}
1280
+
1281
+ # Test None annotation (should be treated as Any)
1282
+ validate_full_roundtrip(instance, Config, (expected_dict, None))
1283
+
1284
+
1285
+ def test_roundtrip_struct_to_dict_nested() -> None:
1286
+ """Test struct -> dict binding with nested structs."""
1287
+
1288
+ @dataclass
1289
+ class Address:
1290
+ street: str
1291
+ city: str
1292
+
1293
+ @dataclass
1294
+ class Person:
1295
+ name: str
1296
+ age: int
1297
+ address: Address
1298
+
1299
+ address = Address("123 Main St", "Anytown")
1300
+ person = Person("John", 30, address)
1301
+ expected_dict = {
1302
+ "name": "John",
1303
+ "age": 30,
1304
+ "address": {"street": "123 Main St", "city": "Anytown"},
1305
+ }
1306
+
1307
+ # Test nested struct conversion
1308
+ validate_full_roundtrip(person, Person, (expected_dict, dict[str, Any]))
1309
+
1310
+
1311
+ def test_roundtrip_struct_to_dict_with_list() -> None:
1312
+ """Test struct -> dict binding with list fields."""
1313
+
1314
+ @dataclass
1315
+ class Team:
1316
+ name: str
1317
+ members: list[str]
1318
+ active: bool
1319
+
1320
+ instance = Team("Dev Team", ["Alice", "Bob", "Charlie"], True)
1321
+ expected_dict = {
1322
+ "name": "Dev Team",
1323
+ "members": ["Alice", "Bob", "Charlie"],
1324
+ "active": True,
1325
+ }
1326
+
1327
+ validate_full_roundtrip(instance, Team, (expected_dict, dict))
1328
+
1329
+
1330
+ def test_roundtrip_namedtuple_to_dict_binding() -> None:
1331
+ """Test NamedTuple -> dict binding."""
1332
+
1333
+ class Point(NamedTuple):
1334
+ x: float
1335
+ y: float
1336
+ z: float
1337
+
1338
+ instance = Point(1.0, 2.0, 3.0)
1339
+ expected_dict = {"x": 1.0, "y": 2.0, "z": 3.0}
1340
+
1341
+ validate_full_roundtrip(
1342
+ instance, Point, (expected_dict, dict), (expected_dict, Any)
1343
+ )
@@ -539,5 +539,8 @@ def test_invalid_list_kind() -> None:
539
539
 
540
540
  def test_unsupported_type() -> None:
541
541
  typ = set
542
- with pytest.raises(ValueError, match="type unsupported yet: <class 'set'>"):
542
+ with pytest.raises(
543
+ ValueError,
544
+ match="Unsupported as a specific type annotation for CocoIndex data type.*: <class 'set'>",
545
+ ):
543
546
  analyze_type_info(typ)
cocoindex/typing.py CHANGED
@@ -168,7 +168,8 @@ class AnalyzedTypeInfo:
168
168
 
169
169
  def analyze_type_info(t: Any) -> AnalyzedTypeInfo:
170
170
  """
171
- Analyze a Python type and return the analyzed info.
171
+ Analyze a Python type annotation and extract CocoIndex-specific type information.
172
+ Type annotations for specific CocoIndex types are expected. Raises ValueError for Any, empty, or untyped dict types.
172
173
  """
173
174
  if isinstance(t, tuple) and len(t) == 2:
174
175
  kt, vt = t
@@ -239,9 +240,15 @@ def analyze_type_info(t: Any) -> AnalyzedTypeInfo:
239
240
  _ = DtypeRegistry.validate_dtype_and_get_kind(elem_type)
240
241
  vector_info = VectorInfo(dim=None) if vector_info is None else vector_info
241
242
 
242
- elif base_type is collections.abc.Mapping or base_type is dict:
243
+ elif base_type is collections.abc.Mapping or base_type is dict or t is dict:
243
244
  args = typing.get_args(t)
244
- elem_type = (args[0], args[1])
245
+ if len(args) == 0: # Handle untyped dict
246
+ raise ValueError(
247
+ "Untyped dict is not accepted as a specific type annotation; please provide a concrete type, "
248
+ "e.g. a dataclass or namedtuple for Struct types, a dict[str, T] for KTable types."
249
+ )
250
+ else:
251
+ elem_type = (args[0], args[1])
245
252
  kind = "KTable"
246
253
  elif base_type in (types.UnionType, typing.Union):
247
254
  possible_types = typing.get_args(t)
@@ -283,7 +290,9 @@ def analyze_type_info(t: Any) -> AnalyzedTypeInfo:
283
290
  elif t is datetime.timedelta:
284
291
  kind = "TimeDelta"
285
292
  else:
286
- raise ValueError(f"type unsupported yet: {t}")
293
+ raise ValueError(
294
+ f"Unsupported as a specific type annotation for CocoIndex data type (https://cocoindex.io/docs/core/data_types): {t}"
295
+ )
287
296
 
288
297
  return AnalyzedTypeInfo(
289
298
  kind=kind,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: cocoindex
3
- Version: 0.1.65
3
+ Version: 0.1.66
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
@@ -25,7 +25,7 @@ Project-URL: Homepage, https://cocoindex.io/
25
25
  <img src="https://cocoindex.io/images/github.svg" alt="CocoIndex">
26
26
  </p>
27
27
 
28
- <h2 align="center">Extract, Transform, Index Data. Easy and Fresh. 🌴</h2>
28
+ <h1 align="center">Data transformation for AI</h1>
29
29
 
30
30
  <div align="center">
31
31
 
@@ -40,18 +40,32 @@ Project-URL: Homepage, https://cocoindex.io/
40
40
  [![Discord](https://img.shields.io/discord/1314801574169673738?logo=discord&color=5B5BD6&logoColor=white)](https://discord.com/invite/zpA9S2DR7s)
41
41
  </div>
42
42
 
43
- **CocoIndex** is an ultra performant data transformation framework, with its core engine written in Rust. The problem it tries to solve is to make it easy to prepare fresh data for AI - either creating embedding, building knowledge graphs, or performing other data transformations - and take real-time data pipelines beyond traditional SQL.
43
+ Ultra performant data transformation framework for AI, with core engine written in Rust. Support incremental processing and data lineage out-of-box. Exceptional developer velocity. Production-ready at day 0.
44
+
45
+ ⭐ Drop a star to help us grow!
46
+
47
+ </br>
48
+
49
+ <p align="center">
50
+ <img src="https://cocoindex.io/images/transformation.svg" alt="CocoIndex Transformation">
51
+ </p>
52
+
53
+ </br>
54
+
55
+ CocoIndex makes it super easy to transform data with AI workloads, and keep source data and target in sync effortlessly.
56
+
57
+ </br>
44
58
 
45
59
  <p align="center">
46
- <img src="https://cocoindex.io/images/cocoindex-features.png" alt="CocoIndex Features" width="500">
60
+ <img src="https://cocoindex.io/images/venn-features.png" alt="CocoIndex Features" width='480'>
47
61
  </p>
48
62
 
49
- The philosophy is to have the framework handle the source updates, and having developers only worry about defining a series of data transformation, inspired by spreadsheet.
63
+ </br>
50
64
 
51
- ## Dataflow programming
52
- Unlike a workflow orchestration framework where data is usually opaque, in CocoIndex, data and data operations are first class citizens. CocoIndex follows the idea of [Dataflow](https://en.wikipedia.org/wiki/Dataflow_programming) programming model. Each transformation creates a new field solely based on input fields, without hidden states and value mutation. All data before/after each transformation is observable, with lineage out of the box.
65
+ Either creating embedding, building knowledge graphs, or any data transformations - beyond traditional SQL.
53
66
 
54
- **Particularly**, users don't explicitly mutate data by creating, updating and deleting. Rather, they define something like - for a set of source data, this is the transformation or formula. The framework takes care of the data operations such as when to create, update, or delete.
67
+ ## Exceptional velocity
68
+ Just declare transformation in dataflow with ~100 lines of python
55
69
 
56
70
  ```python
57
71
  # import
@@ -69,19 +83,27 @@ collector.collect(...)
69
83
  collector.export(...)
70
84
  ```
71
85
 
72
- ## Data Freshness
73
- As a data framework, CocoIndex takes it to the next level on data freshness. **Incremental processing** is one of the core values provided by CocoIndex.
86
+ CocoIndex follows the idea of [Dataflow](https://en.wikipedia.org/wiki/Dataflow_programming) programming model. Each transformation creates a new field solely based on input fields, without hidden states and value mutation. All data before/after each transformation is observable, with lineage out of the box.
87
+
88
+ **Particularly**, developers don't explicitly mutate data by creating, updating and deleting. They just need to define transformation/formula for a set of source data.
89
+
90
+ ## Build like LEGO
91
+ Native builtins for different source, targets and transformations. Standardize interface, make it 1-line code switch between different components.
74
92
 
75
93
  <p align="center">
76
- <img src="https://github.com/user-attachments/assets/f4eb29b3-84ee-4fa0-a1e2-80eedeeabde6" alt="Incremental Processing" width="700">
94
+ <img src="https://cocoindex.io/images/components.svg" alt="CocoIndex Features">
77
95
  </p>
78
96
 
79
- The frameworks takes care of
80
- - Change data capture.
81
- - Figure out what exactly needs to be updated, and only updating that without having to recompute everything.
97
+ ## Data Freshness
98
+ CocoIndex keep source data and target in sync effortlessly.
82
99
 
83
- This makes it fast to reflect any source updates to the target store. If you have concerns with surfacing stale data to AI agents and are spending lots of efforts working on infra piece to optimize the latency, the framework actually handles it for you.
100
+ <p align="center">
101
+ <img src="https://github.com/user-attachments/assets/f4eb29b3-84ee-4fa0-a1e2-80eedeeabde6" alt="Incremental Processing" width="700">
102
+ </p>
84
103
 
104
+ It has out-of-box support for incremental indexing:
105
+ - minimal recomputation on source or logic change.
106
+ - (re-)processing necessary portions; reuse cache when possible
85
107
 
86
108
  ## Quick Start:
87
109
  If you're new to CocoIndex, we recommend checking out
@@ -100,7 +122,7 @@ pip install -U cocoindex
100
122
  2. [Install Postgres](https://cocoindex.io/docs/getting_started/installation#-install-postgres) if you don't have one. CocoIndex uses it for incremental processing.
101
123
 
102
124
 
103
- ### Define data flow
125
+ ## Define data flow
104
126
 
105
127
  Follow [Quick Start Guide](https://cocoindex.io/docs/getting_started/quickstart) to define your first indexing flow. An example flow looks like:
106
128
 
@@ -144,8 +166,9 @@ def text_embedding_flow(flow_builder: cocoindex.FlowBuilder, data_scope: cocoind
144
166
 
145
167
  It defines an index flow like this:
146
168
 
147
- <img width="363" alt="Data Flow" src="https://github.com/user-attachments/assets/2ea7be6d-3d94-42b1-b2bd-22515577e463" />
148
-
169
+ <p align="center">
170
+ <img width="400" alt="Data Flow" src="https://github.com/user-attachments/assets/2ea7be6d-3d94-42b1-b2bd-22515577e463" />
171
+ </p>
149
172
 
150
173
  ## 🚀 Examples and demo
151
174
 
@@ -156,6 +179,7 @@ It defines an index flow like this:
156
179
  | [PDF Embedding](examples/pdf_embedding) | Parse PDF and index text embeddings for semantic search |
157
180
  | [Manuals LLM Extraction](examples/manuals_llm_extraction) | Extract structured information from a manual using LLM |
158
181
  | [Amazon S3 Embedding](examples/amazon_s3_embedding) | Index text documents from Amazon S3 |
182
+ | [Azure Blob Storage Embedding](examples/azure_blob_embedding) | Index text documents from Azure Blob Storage |
159
183
  | [Google Drive Text Embedding](examples/gdrive_text_embedding) | Index text documents from Google Drive |
160
184
  | [Docs to Knowledge Graph](examples/docs_to_knowledge_graph) | Extract relationships from Markdown documents and build a knowledge graph |
161
185
  | [Embeddings to Qdrant](examples/text_embedding_qdrant) | Index documents in a Qdrant collection for semantic search |
@@ -1,28 +1,28 @@
1
- cocoindex-0.1.65.dist-info/METADATA,sha256=q_r8liwwqP1fcDFl0sNkTjty5ZZt6xbGEUFcpOOOR04,10136
2
- cocoindex-0.1.65.dist-info/WHEEL,sha256=7zxhuuBJUuil6R_MfVJvTZXJmNSPMntESIcLB40BBxA,116
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.pypy311-pp73-aarch64-linux-gnu.so,sha256=zyMcIKPC6h3SZcET-K5b2uMr8UOs7grRdOzh7uVFlBI,61389384
1
+ cocoindex-0.1.66.dist-info/METADATA,sha256=ZwdUYvaVBt5qmZ7pQqJOB6FoszGiwbEerJyeI52aSBE,10098
2
+ cocoindex-0.1.66.dist-info/WHEEL,sha256=7zxhuuBJUuil6R_MfVJvTZXJmNSPMntESIcLB40BBxA,116
3
+ cocoindex-0.1.66.dist-info/entry_points.txt,sha256=_NretjYVzBdNTn7dK-zgwr7YfG2afz1u1uSE-5bZXF8,46
4
+ cocoindex-0.1.66.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
5
+ cocoindex/__init__.py,sha256=nr-W-LX0e9EX6ZVMbf0MRF0EhtntqADOdwc9F2nwfWw,2004
6
+ cocoindex/_engine.pypy311-pp73-aarch64-linux-gnu.so,sha256=B6HiMN0h56MtAcVy_f6s3KOXjvVf1jURJcwSoEMxL6o,64916680
7
7
  cocoindex/auth_registry.py,sha256=1XqO7ibjmBBd8i11XSJTvTgdz8p1ptW-ZpuSgo_5zzk,716
8
8
  cocoindex/cli.py,sha256=-gp639JSyQN6YjnhGqCakIzYoSSqXxQMbxbkcYGP0QY,22359
9
- cocoindex/convert.py,sha256=qE1Ut_tAwX4wA4WqaWxpyj80-1t6WZ8Oi5_L9Mw5g4k,11393
9
+ cocoindex/convert.py,sha256=RYfRUungabr-dHakG4k2kDvYambxHFljAmTuPQeQths,13117
10
10
  cocoindex/flow.py,sha256=MFPtfJBVTjQ56d7vUn2LvtY30Vg4q2rY6nqvjjJL1kQ,35085
11
- cocoindex/functions.py,sha256=IBwvdPpGR-S5mk53HvHpT2GVs15MI9wQznxgOdxA0ac,3202
11
+ cocoindex/functions.py,sha256=EvRnd2Y3TIeEueYjOmproBG5RinlbqK6ym1Hnrmnm-0,3251
12
12
  cocoindex/index.py,sha256=j93B9jEvvLXHtpzKWL88SY6wCGEoPgpsQhEGHlyYGFg,540
13
13
  cocoindex/lib.py,sha256=f--9dAYd84CZosbDZqNW0oGbBLsY3dXiUTR1VrfQ_QY,817
14
- cocoindex/llm.py,sha256=0ri8ZRg9_Zf2gyC5xuQ1Kq6kdZUO8r-A5WLnxit5S_4,448
14
+ cocoindex/llm.py,sha256=WxmWUbNcf9HOCM5xkbDeFs9lF67M3mr810B7deDDc-8,673
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
18
  cocoindex/setting.py,sha256=ADuv7RaWd9k-m3V0Cfy2jmaCt6DupJCviWdOm0CTiVw,4734
19
19
  cocoindex/setup.py,sha256=7uIHKN4FOCuoidPXcKyGTrkqpkl9luL49-6UcnMxYzw,3068
20
- cocoindex/sources.py,sha256=JCnOhv1w4o28e03i7yvo4ESicWYAhckkBg5bQlxNH4U,1330
20
+ cocoindex/sources.py,sha256=8MR_oyr7t0m-gUFq7FO6HHM-tDLmQSBAjheFXJzRd8g,1733
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=efwF-43SFJiu85sQ78Z9k9OaJphTz_es_1cm5BoPO2Y,42565
23
+ cocoindex/tests/test_convert.py,sha256=48-fnWKv02gmFETV3b-8IC89SKMUZfJLEH-ucRtXGuI,45450
24
24
  cocoindex/tests/test_optional_database.py,sha256=snAmkNa6wtOSaxoZE1HgjvL5v_ylitt3Jt_9df4Cgdc,8506
25
- cocoindex/tests/test_typing.py,sha256=t6UCYShcfonTfjBlGRWPiFGMZ8DGFfABXo6idekPoJE,14757
26
- cocoindex/typing.py,sha256=qQ-nSdkHzu8pSxfuR5sGGfoE8nCKqCDb0D9jbmxVt4M,12635
25
+ cocoindex/tests/test_typing.py,sha256=NB4nUzoumOF_wGFa4D2Xf6d0bUVtOiSXyb78M1pYSG4,14827
26
+ cocoindex/typing.py,sha256=MO9HkrNpargvMPvpkd7jgSu2R-21KE_NaB9-WI4YOZA,13241
27
27
  cocoindex/utils.py,sha256=hUhX-XV6XGCtJSEIpBOuDv6VvqImwPlgBxztBTw7u0U,598
28
- cocoindex-0.1.65.dist-info/RECORD,,
28
+ cocoindex-0.1.66.dist-info/RECORD,,