cocoindex 0.1.73__cp311-cp311-macosx_10_12_x86_64.whl → 0.1.75__cp311-cp311-macosx_10_12_x86_64.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
@@ -11,7 +11,8 @@ 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, FlowUpdaterStatusUpdates
14
- from .flow import add_flow_def, remove_flow
14
+ from .flow import open_flow
15
+ from .flow import add_flow_def, remove_flow # DEPRECATED
15
16
  from .flow import update_all_flows_async, setup_all_flows, drop_all_flows
16
17
  from .lib import init, start_server, stop
17
18
  from .llm import LlmSpec, LlmApiType
@@ -57,8 +58,9 @@ __all__ = [
57
58
  "FlowLiveUpdater",
58
59
  "FlowLiveUpdaterOptions",
59
60
  "FlowUpdaterStatusUpdates",
60
- "add_flow_def",
61
- "remove_flow",
61
+ "open_flow",
62
+ "add_flow_def", # DEPRECATED
63
+ "remove_flow", # DEPRECATED
62
64
  "update_all_flows_async",
63
65
  "setup_all_flows",
64
66
  "drop_all_flows",
Binary file
cocoindex/convert.py CHANGED
@@ -2,9 +2,12 @@
2
2
  Utilities to convert between Python and engine values.
3
3
  """
4
4
 
5
+ from __future__ import annotations
6
+
5
7
  import dataclasses
6
8
  import datetime
7
9
  import inspect
10
+ import warnings
8
11
  from enum import Enum
9
12
  from typing import Any, Callable, Mapping, get_origin
10
13
 
@@ -29,6 +32,24 @@ from .typing import (
29
32
  )
30
33
 
31
34
 
35
+ class ChildFieldPath:
36
+ """Context manager to append a field to field_path on enter and pop it on exit."""
37
+
38
+ _field_path: list[str]
39
+ _field_name: str
40
+
41
+ def __init__(self, field_path: list[str], field_name: str):
42
+ self._field_path: list[str] = field_path
43
+ self._field_name = field_name
44
+
45
+ def __enter__(self) -> ChildFieldPath:
46
+ self._field_path.append(self._field_name)
47
+ return self
48
+
49
+ def __exit__(self, _exc_type: Any, _exc_val: Any, _exc_tb: Any) -> None:
50
+ self._field_path.pop()
51
+
52
+
32
53
  def encode_engine_value(value: Any) -> Any:
33
54
  """Encode a Python value to an engine value."""
34
55
  if dataclasses.is_dataclass(value):
@@ -73,7 +94,8 @@ def _is_type_kind_convertible_to(src_type_kind: str, dst_type_kind: str) -> bool
73
94
  def make_engine_value_decoder(
74
95
  field_path: list[str],
75
96
  src_type: dict[str, Any],
76
- dst_annotation: Any,
97
+ dst_type_info: AnalyzedTypeInfo,
98
+ for_key: bool = False,
77
99
  ) -> Callable[[Any], Any]:
78
100
  """
79
101
  Make a decoder from an engine value to a Python value.
@@ -89,7 +111,6 @@ def make_engine_value_decoder(
89
111
 
90
112
  src_type_kind = src_type["kind"]
91
113
 
92
- dst_type_info = analyze_type_info(dst_annotation)
93
114
  dst_type_variant = dst_type_info.variant
94
115
 
95
116
  if isinstance(dst_type_variant, AnalyzedUnknownType):
@@ -99,96 +120,100 @@ def make_engine_value_decoder(
99
120
  )
100
121
 
101
122
  if src_type_kind == "Struct":
102
- return _make_engine_struct_value_decoder(
123
+ return make_engine_struct_decoder(
103
124
  field_path,
104
125
  src_type["fields"],
105
126
  dst_type_info,
127
+ for_key=for_key,
106
128
  )
107
129
 
108
130
  if src_type_kind in TABLE_TYPES:
109
- field_path.append("[*]")
110
- engine_fields_schema = src_type["row"]["fields"]
111
-
112
- if src_type_kind == "LTable":
113
- if isinstance(dst_type_variant, AnalyzedAnyType):
114
- return _make_engine_ltable_to_list_dict_decoder(
115
- field_path, engine_fields_schema
116
- )
117
- if not isinstance(dst_type_variant, AnalyzedListType):
118
- raise ValueError(
119
- f"Type mismatch for `{''.join(field_path)}`: "
120
- f"declared `{dst_type_info.core_type}`, a list type expected"
131
+ with ChildFieldPath(field_path, "[*]"):
132
+ engine_fields_schema = src_type["row"]["fields"]
133
+
134
+ if src_type_kind == "LTable":
135
+ if isinstance(dst_type_variant, AnalyzedAnyType):
136
+ dst_elem_type = Any
137
+ elif isinstance(dst_type_variant, AnalyzedListType):
138
+ dst_elem_type = dst_type_variant.elem_type
139
+ else:
140
+ raise ValueError(
141
+ f"Type mismatch for `{''.join(field_path)}`: "
142
+ f"declared `{dst_type_info.core_type}`, a list type expected"
143
+ )
144
+ row_decoder = make_engine_struct_decoder(
145
+ field_path,
146
+ engine_fields_schema,
147
+ analyze_type_info(dst_elem_type),
121
148
  )
122
- row_decoder = _make_engine_struct_value_decoder(
123
- field_path,
124
- engine_fields_schema,
125
- analyze_type_info(dst_type_variant.elem_type),
126
- )
127
149
 
128
- def decode(value: Any) -> Any | None:
129
- if value is None:
130
- return None
131
- return [row_decoder(v) for v in value]
150
+ def decode(value: Any) -> Any | None:
151
+ if value is None:
152
+ return None
153
+ return [row_decoder(v) for v in value]
154
+
155
+ elif src_type_kind == "KTable":
156
+ if isinstance(dst_type_variant, AnalyzedAnyType):
157
+ key_type, value_type = Any, Any
158
+ elif isinstance(dst_type_variant, AnalyzedDictType):
159
+ key_type = dst_type_variant.key_type
160
+ value_type = dst_type_variant.value_type
161
+ else:
162
+ raise ValueError(
163
+ f"Type mismatch for `{''.join(field_path)}`: "
164
+ f"declared `{dst_type_info.core_type}`, a dict type expected"
165
+ )
132
166
 
133
- elif src_type_kind == "KTable":
134
- if isinstance(dst_type_variant, AnalyzedAnyType):
135
- return _make_engine_ktable_to_dict_dict_decoder(
136
- field_path, engine_fields_schema
167
+ key_field_schema = engine_fields_schema[0]
168
+ field_path.append(f".{key_field_schema.get('name', KEY_FIELD_NAME)}")
169
+ key_decoder = make_engine_value_decoder(
170
+ field_path,
171
+ key_field_schema["type"],
172
+ analyze_type_info(key_type),
173
+ for_key=True,
137
174
  )
138
- if not isinstance(dst_type_variant, AnalyzedDictType):
139
- raise ValueError(
140
- f"Type mismatch for `{''.join(field_path)}`: "
141
- f"declared `{dst_type_info.core_type}`, a dict type expected"
175
+ field_path.pop()
176
+ value_decoder = make_engine_struct_decoder(
177
+ field_path,
178
+ engine_fields_schema[1:],
179
+ analyze_type_info(value_type),
142
180
  )
143
181
 
144
- key_field_schema = engine_fields_schema[0]
145
- field_path.append(f".{key_field_schema.get('name', KEY_FIELD_NAME)}")
146
- key_decoder = make_engine_value_decoder(
147
- field_path, key_field_schema["type"], dst_type_variant.key_type
148
- )
149
- field_path.pop()
150
- value_decoder = _make_engine_struct_value_decoder(
151
- field_path,
152
- engine_fields_schema[1:],
153
- analyze_type_info(dst_type_variant.value_type),
154
- )
155
-
156
- def decode(value: Any) -> Any | None:
157
- if value is None:
158
- return None
159
- return {key_decoder(v[0]): value_decoder(v[1:]) for v in value}
182
+ def decode(value: Any) -> Any | None:
183
+ if value is None:
184
+ return None
185
+ return {key_decoder(v[0]): value_decoder(v[1:]) for v in value}
160
186
 
161
- field_path.pop()
162
187
  return decode
163
188
 
164
189
  if src_type_kind == "Union":
165
190
  if isinstance(dst_type_variant, AnalyzedAnyType):
166
191
  return lambda value: value[1]
167
192
 
168
- dst_type_variants = (
169
- dst_type_variant.variant_types
193
+ dst_type_info_variants = (
194
+ [analyze_type_info(t) for t in dst_type_variant.variant_types]
170
195
  if isinstance(dst_type_variant, AnalyzedUnionType)
171
- else [dst_annotation]
196
+ else [dst_type_info]
172
197
  )
173
198
  src_type_variants = src_type["types"]
174
199
  decoders = []
175
200
  for i, src_type_variant in enumerate(src_type_variants):
176
- src_field_path = field_path + [f"[{i}]"]
177
- decoder = None
178
- for dst_type_variant in dst_type_variants:
179
- try:
180
- decoder = make_engine_value_decoder(
181
- src_field_path, src_type_variant, dst_type_variant
201
+ with ChildFieldPath(field_path, f"[{i}]"):
202
+ decoder = None
203
+ for dst_type_info_variant in dst_type_info_variants:
204
+ try:
205
+ decoder = make_engine_value_decoder(
206
+ field_path, src_type_variant, dst_type_info_variant
207
+ )
208
+ break
209
+ except ValueError:
210
+ pass
211
+ if decoder is None:
212
+ raise ValueError(
213
+ f"Type mismatch for `{''.join(field_path)}`: "
214
+ f"cannot find matched target type for source type variant {src_type_variant}"
182
215
  )
183
- break
184
- except ValueError:
185
- pass
186
- if decoder is None:
187
- raise ValueError(
188
- f"Type mismatch for `{''.join(field_path)}`: "
189
- f"cannot find matched target type for source type variant {src_type_variant}"
190
- )
191
- decoders.append(decoder)
216
+ decoders.append(decoder)
192
217
  return lambda value: decoders[value[0]](value[1])
193
218
 
194
219
  if isinstance(dst_type_variant, AnalyzedAnyType):
@@ -216,7 +241,9 @@ def make_engine_value_decoder(
216
241
  vec_elem_decoder = make_engine_value_decoder(
217
242
  field_path + ["[*]"],
218
243
  src_type["element_type"],
219
- dst_type_variant and dst_type_variant.elem_type,
244
+ analyze_type_info(
245
+ dst_type_variant.elem_type if dst_type_variant else Any
246
+ ),
220
247
  )
221
248
 
222
249
  def decode_vector(value: Any) -> Any | None:
@@ -247,7 +274,7 @@ def make_engine_value_decoder(
247
274
  if not _is_type_kind_convertible_to(src_type_kind, dst_type_variant.kind):
248
275
  raise ValueError(
249
276
  f"Type mismatch for `{''.join(field_path)}`: "
250
- f"passed in {src_type_kind}, declared {dst_annotation} ({dst_type_variant.kind})"
277
+ f"passed in {src_type_kind}, declared {dst_type_info.core_type} ({dst_type_variant.kind})"
251
278
  )
252
279
 
253
280
  if dst_type_variant.kind in ("Float32", "Float64", "Int64"):
@@ -267,30 +294,54 @@ def make_engine_value_decoder(
267
294
  return lambda value: value
268
295
 
269
296
 
270
- def _make_engine_struct_value_decoder(
297
+ def _get_auto_default_for_type(
298
+ type_info: AnalyzedTypeInfo,
299
+ ) -> tuple[Any, bool]:
300
+ """
301
+ Get an auto-default value for a type annotation if it's safe to do so.
302
+
303
+ Returns:
304
+ A tuple of (default_value, is_supported) where:
305
+ - default_value: The default value if auto-defaulting is supported
306
+ - is_supported: True if auto-defaulting is supported for this type
307
+ """
308
+ # Case 1: Nullable types (Optional[T] or T | None)
309
+ if type_info.nullable:
310
+ return None, True
311
+
312
+ # Case 2: Table types (KTable or LTable) - check if it's a list or dict type
313
+ if isinstance(type_info.variant, AnalyzedListType):
314
+ return [], True
315
+ elif isinstance(type_info.variant, AnalyzedDictType):
316
+ return {}, True
317
+
318
+ return None, False
319
+
320
+
321
+ def make_engine_struct_decoder(
271
322
  field_path: list[str],
272
323
  src_fields: list[dict[str, Any]],
273
324
  dst_type_info: AnalyzedTypeInfo,
325
+ for_key: bool = False,
274
326
  ) -> Callable[[list[Any]], Any]:
275
327
  """Make a decoder from an engine field values to a Python value."""
276
328
 
277
329
  dst_type_variant = dst_type_info.variant
278
330
 
279
- use_dict = False
280
331
  if isinstance(dst_type_variant, AnalyzedAnyType):
281
- use_dict = True
332
+ if for_key:
333
+ return _make_engine_struct_to_tuple_decoder(field_path, src_fields)
334
+ else:
335
+ return _make_engine_struct_to_dict_decoder(field_path, src_fields, Any)
282
336
  elif isinstance(dst_type_variant, AnalyzedDictType):
283
337
  analyzed_key_type = analyze_type_info(dst_type_variant.key_type)
284
- analyzed_value_type = analyze_type_info(dst_type_variant.value_type)
285
- use_dict = (
338
+ if (
286
339
  isinstance(analyzed_key_type.variant, AnalyzedAnyType)
287
- or (
288
- isinstance(analyzed_key_type.variant, AnalyzedBasicType)
289
- and analyzed_key_type.variant.kind == "Str"
340
+ or analyzed_key_type.core_type is str
341
+ ):
342
+ return _make_engine_struct_to_dict_decoder(
343
+ field_path, src_fields, dst_type_variant.value_type
290
344
  )
291
- ) and isinstance(analyzed_value_type.variant, AnalyzedAnyType)
292
- if use_dict:
293
- return _make_engine_struct_to_dict_decoder(field_path, src_fields)
294
345
 
295
346
  if not isinstance(dst_type_variant, AnalyzedStructType):
296
347
  raise ValueError(
@@ -321,32 +372,39 @@ def _make_engine_struct_value_decoder(
321
372
  else:
322
373
  raise ValueError(f"Unsupported struct type: {dst_struct_type}")
323
374
 
324
- def make_closure_for_value(
375
+ def make_closure_for_field(
325
376
  name: str, param: inspect.Parameter
326
377
  ) -> Callable[[list[Any]], Any]:
327
378
  src_idx = src_name_to_idx.get(name)
328
- if src_idx is not None:
329
- field_path.append(f".{name}")
330
- field_decoder = make_engine_value_decoder(
331
- field_path, src_fields[src_idx]["type"], param.annotation
332
- )
333
- field_path.pop()
334
- return (
335
- lambda values: field_decoder(values[src_idx])
336
- if len(values) > src_idx
337
- else param.default
338
- )
379
+ type_info = analyze_type_info(param.annotation)
380
+
381
+ with ChildFieldPath(field_path, f".{name}"):
382
+ if src_idx is not None:
383
+ field_decoder = make_engine_value_decoder(
384
+ field_path, src_fields[src_idx]["type"], type_info, for_key=for_key
385
+ )
386
+ return lambda values: field_decoder(values[src_idx])
387
+
388
+ default_value = param.default
389
+ if default_value is not inspect.Parameter.empty:
390
+ return lambda _: default_value
391
+
392
+ auto_default, is_supported = _get_auto_default_for_type(type_info)
393
+ if is_supported:
394
+ warnings.warn(
395
+ f"Field '{name}' (type {param.annotation}) without default value is missing in input: "
396
+ f"{''.join(field_path)}. Auto-assigning default value: {auto_default}",
397
+ UserWarning,
398
+ stacklevel=4,
399
+ )
400
+ return lambda _: auto_default
339
401
 
340
- default_value = param.default
341
- if default_value is inspect.Parameter.empty:
342
402
  raise ValueError(
343
- f"Field without default value is missing in input: {''.join(field_path)}"
403
+ f"Field '{name}' (type {param.annotation}) without default value is missing in input: {''.join(field_path)}"
344
404
  )
345
405
 
346
- return lambda _: default_value
347
-
348
406
  field_value_decoder = [
349
- make_closure_for_value(name, param) for (name, param) in parameters.items()
407
+ make_closure_for_field(name, param) for (name, param) in parameters.items()
350
408
  ]
351
409
 
352
410
  return lambda values: dst_struct_type(
@@ -357,19 +415,20 @@ def _make_engine_struct_value_decoder(
357
415
  def _make_engine_struct_to_dict_decoder(
358
416
  field_path: list[str],
359
417
  src_fields: list[dict[str, Any]],
418
+ value_type_annotation: Any,
360
419
  ) -> Callable[[list[Any] | None], dict[str, Any] | None]:
361
420
  """Make a decoder from engine field values to a Python dict."""
362
421
 
363
422
  field_decoders = []
364
- for i, field_schema in enumerate(src_fields):
423
+ value_type_info = analyze_type_info(value_type_annotation)
424
+ for field_schema in src_fields:
365
425
  field_name = field_schema["name"]
366
- field_path.append(f".{field_name}")
367
- field_decoder = make_engine_value_decoder(
368
- field_path,
369
- field_schema["type"],
370
- Any, # Use Any for recursive decoding
371
- )
372
- field_path.pop()
426
+ with ChildFieldPath(field_path, f".{field_name}"):
427
+ field_decoder = make_engine_value_decoder(
428
+ field_path,
429
+ field_schema["type"],
430
+ value_type_info,
431
+ )
373
432
  field_decoders.append((field_name, field_decoder))
374
433
 
375
434
  def decode_to_dict(values: list[Any] | None) -> dict[str, Any] | None:
@@ -387,75 +446,37 @@ def _make_engine_struct_to_dict_decoder(
387
446
  return decode_to_dict
388
447
 
389
448
 
390
- def _make_engine_ltable_to_list_dict_decoder(
449
+ def _make_engine_struct_to_tuple_decoder(
391
450
  field_path: list[str],
392
451
  src_fields: list[dict[str, Any]],
393
- ) -> Callable[[list[Any] | None], list[dict[str, Any]] | None]:
394
- """Make a decoder from engine LTable values to a list of dicts."""
395
-
396
- # Create a decoder for each row (struct) to dict
397
- row_decoder = _make_engine_struct_to_dict_decoder(field_path, src_fields)
452
+ ) -> Callable[[list[Any] | None], tuple[Any, ...] | None]:
453
+ """Make a decoder from engine field values to a Python tuple."""
398
454
 
399
- def decode_to_list_dict(values: list[Any] | None) -> list[dict[str, Any]] | None:
400
- if values is None:
401
- return None
402
- result = []
403
- for i, row_values in enumerate(values):
404
- decoded_row = row_decoder(row_values)
405
- if decoded_row is None:
406
- raise ValueError(
407
- f"LTable row at index {i} decoded to None, which is not allowed."
455
+ field_decoders = []
456
+ value_type_info = analyze_type_info(Any)
457
+ for field_schema in src_fields:
458
+ field_name = field_schema["name"]
459
+ with ChildFieldPath(field_path, f".{field_name}"):
460
+ field_decoders.append(
461
+ make_engine_value_decoder(
462
+ field_path,
463
+ field_schema["type"],
464
+ value_type_info,
408
465
  )
409
- result.append(decoded_row)
410
- return result
411
-
412
- return decode_to_list_dict
413
-
414
-
415
- def _make_engine_ktable_to_dict_dict_decoder(
416
- field_path: list[str],
417
- src_fields: list[dict[str, Any]],
418
- ) -> Callable[[list[Any] | None], dict[Any, dict[str, Any]] | None]:
419
- """Make a decoder from engine KTable values to a dict of dicts."""
420
-
421
- if not src_fields:
422
- raise ValueError("KTable must have at least one field for the key")
423
-
424
- # First field is the key, remaining fields are the value
425
- key_field_schema = src_fields[0]
426
- value_fields_schema = src_fields[1:]
427
-
428
- # Create decoders
429
- field_path.append(f".{key_field_schema.get('name', KEY_FIELD_NAME)}")
430
- key_decoder = make_engine_value_decoder(field_path, key_field_schema["type"], Any)
431
- field_path.pop()
432
-
433
- value_decoder = _make_engine_struct_to_dict_decoder(field_path, value_fields_schema)
466
+ )
434
467
 
435
- def decode_to_dict_dict(
436
- values: list[Any] | None,
437
- ) -> dict[Any, dict[str, Any]] | None:
468
+ def decode_to_tuple(values: list[Any] | None) -> tuple[Any, ...] | None:
438
469
  if values is None:
439
470
  return None
440
- result = {}
441
- for row_values in values:
442
- if not row_values:
443
- raise ValueError("KTable row must have at least 1 value (the key)")
444
- key = key_decoder(row_values[0])
445
- if len(row_values) == 1:
446
- value: dict[str, Any] = {}
447
- else:
448
- tmp = value_decoder(row_values[1:])
449
- if tmp is None:
450
- value = {}
451
- else:
452
- value = tmp
453
- if isinstance(key, dict):
454
- key = tuple(key.values())
455
- result[key] = value
456
- return result
471
+ if len(field_decoders) != len(values):
472
+ raise ValueError(
473
+ f"Field count mismatch: expected {len(field_decoders)}, got {len(values)}"
474
+ )
475
+ return tuple(
476
+ field_decoder(value) for value, field_decoder in zip(values, field_decoders)
477
+ )
457
478
 
458
- return decode_to_dict_dict
479
+ return decode_to_tuple
459
480
 
460
481
 
461
482
  def dump_engine_object(v: Any) -> Any:
cocoindex/flow.py CHANGED
@@ -16,6 +16,7 @@ from .validation import (
16
16
  validate_full_flow_name,
17
17
  validate_target_name,
18
18
  )
19
+ from .typing import analyze_type_info
19
20
 
20
21
  from dataclasses import dataclass
21
22
  from enum import Enum
@@ -797,7 +798,7 @@ class Flow:
797
798
  The current instance is still valid after it's called.
798
799
  For example, you can still call `setup()` after it, to setup the persistent backends again.
799
800
 
800
- Call `cocoindex.remove_flow()` if you want to remove the flow from the current process.
801
+ Call `close()` if you want to remove the flow from the current process.
801
802
  """
802
803
  execution_context.run(self.drop_async(report_to_stdout=report_to_stdout))
803
804
 
@@ -809,6 +810,18 @@ class Flow:
809
810
  report_to_stdout=report_to_stdout
810
811
  )
811
812
 
813
+ def close(self) -> None:
814
+ """
815
+ Close the flow. It will remove the flow from the current process to free up resources.
816
+ After it's called, methods of the flow should no longer be called.
817
+
818
+ This will NOT touch the persistent backends of the flow.
819
+ """
820
+ _engine.remove_flow_context(self.full_name)
821
+ self._lazy_engine_flow = None
822
+ with _flows_lock:
823
+ del _flows[self.name]
824
+
812
825
 
813
826
  def _create_lazy_flow(
814
827
  name: str | None, fl_def: Callable[[FlowBuilder, DataScope], None]
@@ -844,7 +857,10 @@ def get_flow_full_name(name: str) -> str:
844
857
  return f"{setting.get_app_namespace(trailing_delimiter='.')}{name}"
845
858
 
846
859
 
847
- def add_flow_def(name: str, fl_def: Callable[[FlowBuilder, DataScope], None]) -> Flow:
860
+ def open_flow(name: str, fl_def: Callable[[FlowBuilder, DataScope], None]) -> Flow:
861
+ """
862
+ Open a flow, with the given name and definition.
863
+ """
848
864
  with _flows_lock:
849
865
  if name in _flows:
850
866
  raise KeyError(f"Flow with name {name} already exists")
@@ -852,17 +868,18 @@ def add_flow_def(name: str, fl_def: Callable[[FlowBuilder, DataScope], None]) ->
852
868
  return fl
853
869
 
854
870
 
855
- def remove_flow(fl: Flow) -> None:
871
+ def add_flow_def(name: str, fl_def: Callable[[FlowBuilder, DataScope], None]) -> Flow:
856
872
  """
857
- Remove a flow from the current process to free up resources.
858
- After it's called, methods of the flow should no longer be called.
873
+ DEPRECATED: Use `open_flow()` instead.
874
+ """
875
+ return open_flow(name, fl_def)
876
+
859
877
 
860
- This will NOT touch the persistent backends of the flow.
878
+ def remove_flow(fl: Flow) -> None:
861
879
  """
862
- _engine.remove_flow_context(fl.full_name)
863
- fl._lazy_engine_flow = None # pylint: disable=protected-access
864
- with _flows_lock:
865
- del _flows[fl.name]
880
+ DEPRECATED: Use `Flow.close()` instead.
881
+ """
882
+ fl.close()
866
883
 
867
884
 
868
885
  def flow_def(
@@ -871,7 +888,7 @@ def flow_def(
871
888
  """
872
889
  A decorator to wrap the flow definition.
873
890
  """
874
- return lambda fl_def: add_flow_def(name or fl_def.__name__, fl_def)
891
+ return lambda fl_def: open_flow(name or fl_def.__name__, fl_def)
875
892
 
876
893
 
877
894
  def flow_names() -> list[str]:
@@ -1053,7 +1070,7 @@ class TransformFlow(Generic[T]):
1053
1070
  sig.return_annotation
1054
1071
  )
1055
1072
  result_decoder = make_engine_value_decoder(
1056
- [], engine_return_type["type"], python_return_type
1073
+ [], engine_return_type["type"], analyze_type_info(python_return_type)
1057
1074
  )
1058
1075
 
1059
1076
  return TransformFlowInfo(engine_flow, result_decoder)