cocoindex 0.1.40__cp313-cp313-macosx_11_0_arm64.whl → 0.1.42__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 +5 -4
- cocoindex/_engine.cpython-313-darwin.so +0 -0
- cocoindex/cli.py +269 -70
- cocoindex/flow.py +14 -7
- cocoindex/lib.py +42 -49
- cocoindex/typing.py +2 -0
- {cocoindex-0.1.40.dist-info → cocoindex-0.1.42.dist-info}/METADATA +2 -1
- {cocoindex-0.1.40.dist-info → cocoindex-0.1.42.dist-info}/RECORD +11 -10
- cocoindex-0.1.42.dist-info/entry_points.txt +2 -0
- {cocoindex-0.1.40.dist-info → cocoindex-0.1.42.dist-info}/WHEEL +0 -0
- {cocoindex-0.1.40.dist-info → cocoindex-0.1.42.dist-info}/licenses/LICENSE +0 -0
cocoindex/__init__.py
CHANGED
@@ -2,14 +2,15 @@
|
|
2
2
|
Cocoindex is a framework for building and running indexing pipelines.
|
3
3
|
"""
|
4
4
|
from . import functions, query, sources, storages, cli, utils
|
5
|
-
|
5
|
+
|
6
|
+
from .auth_registry import AuthEntryReference, add_auth_entry, ref_auth_entry
|
7
|
+
from .flow import FlowBuilder, DataScope, DataSlice, Flow, transform_flow
|
8
|
+
from .flow import flow_def, flow_def as flow
|
6
9
|
from .flow import EvaluateAndDumpOptions, GeneratedField
|
7
10
|
from .flow import update_all_flows_async, FlowLiveUpdater, FlowLiveUpdaterOptions
|
11
|
+
from .lib import init, start_server, stop, main_fn
|
8
12
|
from .llm import LlmSpec, LlmApiType
|
9
13
|
from .index import VectorSimilarityMetric, VectorIndexDef, IndexOptions
|
10
|
-
from .auth_registry import AuthEntryReference, add_auth_entry, ref_auth_entry
|
11
|
-
from .lib import *
|
12
14
|
from .setting import DatabaseConnectionSpec, Settings, ServerSettings
|
13
15
|
from .setting import get_app_namespace
|
14
|
-
from ._engine import OpArgSchema
|
15
16
|
from .typing import Float32, Float64, LocalDateTime, OffsetDateTime, Range, Vector, Json
|
Binary file
|
cocoindex/cli.py
CHANGED
@@ -1,89 +1,234 @@
|
|
1
1
|
import click
|
2
2
|
import datetime
|
3
|
+
import sys
|
4
|
+
import importlib.util
|
5
|
+
import os
|
6
|
+
import atexit
|
7
|
+
import types
|
3
8
|
|
9
|
+
from dotenv import load_dotenv, find_dotenv
|
4
10
|
from rich.console import Console
|
5
11
|
from rich.table import Table
|
6
12
|
|
7
|
-
from . import flow, lib, setting
|
13
|
+
from . import flow, lib, setting, query
|
8
14
|
from .setup import sync_setup, drop_setup, flow_names_with_setup, apply_setup_changes
|
9
15
|
|
16
|
+
# Create ServerSettings lazily upon first call, as environment variables may be loaded from files, etc.
|
17
|
+
COCOINDEX_HOST = 'https://cocoindex.io'
|
18
|
+
|
19
|
+
def _parse_app_flow_specifier(specifier: str) -> tuple[str, str | None]:
|
20
|
+
"""Parses 'module_or_path[:flow_name]' into (module_or_path, flow_name | None)."""
|
21
|
+
parts = specifier.split(":", 1) # Split only on the first colon
|
22
|
+
app_ref = parts[0]
|
23
|
+
|
24
|
+
if not app_ref:
|
25
|
+
raise click.BadParameter(
|
26
|
+
f"Application module/path part is missing or invalid in specifier: '{specifier}'. "
|
27
|
+
"Expected format like 'myapp.py' or 'myapp:MyFlow'.",
|
28
|
+
param_hint="APP_SPECIFIER"
|
29
|
+
)
|
30
|
+
|
31
|
+
if len(parts) == 1:
|
32
|
+
return app_ref, None
|
33
|
+
|
34
|
+
flow_ref_part = parts[1]
|
35
|
+
|
36
|
+
if not flow_ref_part: # Handles empty string after colon
|
37
|
+
return app_ref, None
|
38
|
+
|
39
|
+
if not flow_ref_part.isidentifier():
|
40
|
+
raise click.BadParameter(
|
41
|
+
f"Invalid format for flow name part ('{flow_ref_part}') in specifier '{specifier}'. "
|
42
|
+
"If a colon separates the application from the flow name, the flow name should typically be "
|
43
|
+
"a valid identifier (e.g., alphanumeric with underscores, not starting with a number).",
|
44
|
+
param_hint="APP_SPECIFIER"
|
45
|
+
)
|
46
|
+
return app_ref, flow_ref_part
|
47
|
+
|
48
|
+
def _get_app_ref_from_specifier(
|
49
|
+
specifier: str,
|
50
|
+
) -> str:
|
51
|
+
"""
|
52
|
+
Parses the APP_TARGET to get the application reference (path or module).
|
53
|
+
Issues a warning if a flow name component is also provided in it.
|
54
|
+
"""
|
55
|
+
app_ref, flow_ref = _parse_app_flow_specifier(specifier)
|
56
|
+
|
57
|
+
if flow_ref is not None:
|
58
|
+
click.echo(
|
59
|
+
click.style(
|
60
|
+
f"Ignoring flow name '{flow_ref}' in '{specifier}': "
|
61
|
+
f"this command operates on the entire app/module '{app_ref}'.",
|
62
|
+
fg='yellow'
|
63
|
+
),
|
64
|
+
err=True
|
65
|
+
)
|
66
|
+
return app_ref
|
67
|
+
|
68
|
+
def _load_user_app(app_target: str) -> types.ModuleType:
|
69
|
+
"""
|
70
|
+
Loads the user's application, which can be a file path or an installed module name.
|
71
|
+
Exits on failure.
|
72
|
+
"""
|
73
|
+
if not app_target:
|
74
|
+
raise click.ClickException("Application target not provided.")
|
75
|
+
|
76
|
+
looks_like_path = os.sep in app_target or app_target.lower().endswith(".py")
|
77
|
+
|
78
|
+
if looks_like_path:
|
79
|
+
if not os.path.isfile(app_target):
|
80
|
+
raise click.ClickException(f"Application file path not found: {app_target}")
|
81
|
+
app_path = os.path.abspath(app_target)
|
82
|
+
app_dir = os.path.dirname(app_path)
|
83
|
+
module_name = os.path.splitext(os.path.basename(app_path))[0]
|
84
|
+
|
85
|
+
if app_dir not in sys.path:
|
86
|
+
sys.path.insert(0, app_dir)
|
87
|
+
try:
|
88
|
+
spec = importlib.util.spec_from_file_location(module_name, app_path)
|
89
|
+
if spec is None:
|
90
|
+
raise ImportError(f"Could not create spec for file: {app_path}")
|
91
|
+
module = importlib.util.module_from_spec(spec)
|
92
|
+
sys.modules[spec.name] = module
|
93
|
+
spec.loader.exec_module(module)
|
94
|
+
return module
|
95
|
+
except (ImportError, FileNotFoundError, PermissionError) as e:
|
96
|
+
raise click.ClickException(f"Failed importing file '{app_path}': {e}")
|
97
|
+
finally:
|
98
|
+
if app_dir in sys.path and sys.path[0] == app_dir:
|
99
|
+
sys.path.pop(0)
|
100
|
+
|
101
|
+
# Try as module
|
102
|
+
try:
|
103
|
+
return importlib.import_module(app_target)
|
104
|
+
except ImportError as e:
|
105
|
+
raise click.ClickException(f"Failed to load module '{app_target}': {e}")
|
106
|
+
except Exception as e:
|
107
|
+
raise click.ClickException(f"Unexpected error importing module '{app_target}': {e}")
|
108
|
+
|
10
109
|
@click.group()
|
11
|
-
|
110
|
+
@click.version_option(package_name="cocoindex", message="%(prog)s version %(version)s")
|
111
|
+
@click.option(
|
112
|
+
"--env-file",
|
113
|
+
type=click.Path(exists=True, file_okay=True, dir_okay=False, readable=True, resolve_path=True),
|
114
|
+
help="Path to a .env file to load environment variables from. "
|
115
|
+
"If not provided, attempts to load '.env' from the current directory.",
|
116
|
+
default=None,
|
117
|
+
show_default=False
|
118
|
+
)
|
119
|
+
def cli(env_file: str | None):
|
12
120
|
"""
|
13
121
|
CLI for Cocoindex.
|
14
122
|
"""
|
123
|
+
dotenv_path = env_file or find_dotenv(usecwd=True)
|
124
|
+
|
125
|
+
if load_dotenv(dotenv_path=dotenv_path):
|
126
|
+
loaded_env_path = os.path.abspath(dotenv_path)
|
127
|
+
click.echo(f"Loaded environment variables from: {loaded_env_path}", err=True)
|
128
|
+
|
129
|
+
try:
|
130
|
+
settings = setting.Settings.from_env()
|
131
|
+
lib.init(settings)
|
132
|
+
atexit.register(lib.stop)
|
133
|
+
except Exception as e:
|
134
|
+
raise click.ClickException(f"Failed to initialize CocoIndex library: {e}")
|
15
135
|
|
16
136
|
@cli.command()
|
17
|
-
@click.
|
18
|
-
|
19
|
-
help="Also show all flows with persisted setup under the current app namespace, even if not defined in the current process.")
|
20
|
-
def ls(show_all: bool):
|
137
|
+
@click.argument("app_target", type=str, required=False)
|
138
|
+
def ls(app_target: str | None):
|
21
139
|
"""
|
22
140
|
List all flows.
|
141
|
+
|
142
|
+
If APP_TARGET (path/to/app.py or a module) is provided, lists flows
|
143
|
+
defined in the app and their backend setup status.
|
144
|
+
|
145
|
+
If APP_TARGET is omitted, lists all flows that have a persisted
|
146
|
+
setup in the backend.
|
23
147
|
"""
|
24
|
-
|
25
|
-
|
26
|
-
|
148
|
+
if app_target:
|
149
|
+
app_ref = _get_app_ref_from_specifier(app_target)
|
150
|
+
_load_user_app(app_ref)
|
27
151
|
|
28
|
-
|
29
|
-
|
152
|
+
current_flow_names = set(flow.flow_names())
|
153
|
+
persisted_flow_names = set(flow_names_with_setup())
|
30
154
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
suffix = ''
|
35
|
-
else:
|
36
|
-
suffix = ' [+]'
|
37
|
-
has_missing_setup = True
|
38
|
-
click.echo(f'{name}{suffix}')
|
155
|
+
if not current_flow_names:
|
156
|
+
click.echo(f"No flows are defined in '{app_ref}'.")
|
157
|
+
return
|
39
158
|
|
40
|
-
|
41
|
-
for name in
|
42
|
-
if name in
|
43
|
-
click.echo(
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
159
|
+
has_missing = False
|
160
|
+
for name in sorted(current_flow_names):
|
161
|
+
if name in persisted_flow_names:
|
162
|
+
click.echo(name)
|
163
|
+
else:
|
164
|
+
click.echo(f"{name} [+]")
|
165
|
+
has_missing = True
|
166
|
+
|
167
|
+
if has_missing:
|
168
|
+
click.echo('')
|
169
|
+
click.echo('Notes:')
|
50
170
|
click.echo(' [+]: Flows present in the current process, but missing setup.')
|
51
|
-
|
52
|
-
|
171
|
+
|
172
|
+
else:
|
173
|
+
persisted_flow_names = sorted(flow_names_with_setup())
|
174
|
+
|
175
|
+
if not persisted_flow_names:
|
176
|
+
click.echo("No persisted flow setups found in the backend.")
|
177
|
+
return
|
178
|
+
|
179
|
+
for name in persisted_flow_names:
|
180
|
+
click.echo(name)
|
53
181
|
|
54
182
|
@cli.command()
|
55
|
-
@click.argument("
|
183
|
+
@click.argument("app_flow_specifier", type=str)
|
56
184
|
@click.option("--color/--no-color", default=True, help="Enable or disable colored output.")
|
57
185
|
@click.option("--verbose", is_flag=True, help="Show verbose output with full details.")
|
58
|
-
def show(
|
186
|
+
def show(app_flow_specifier: str, color: bool, verbose: bool):
|
59
187
|
"""
|
60
|
-
Show the flow spec and schema
|
188
|
+
Show the flow spec and schema.
|
189
|
+
|
190
|
+
APP_FLOW_SPECIFIER: Specifies the application and optionally the target flow.
|
191
|
+
Can be one of the following formats:
|
192
|
+
|
193
|
+
\b
|
194
|
+
- path/to/your_app.py
|
195
|
+
- an_installed.module_name
|
196
|
+
- path/to/your_app.py:SpecificFlowName
|
197
|
+
- an_installed.module_name:SpecificFlowName
|
198
|
+
|
199
|
+
:SpecificFlowName can be omitted only if the application defines a single flow.
|
61
200
|
"""
|
62
|
-
|
63
|
-
|
64
|
-
console.print(flow._render_spec(verbose=verbose))
|
201
|
+
app_ref, flow_ref = _parse_app_flow_specifier(app_flow_specifier)
|
202
|
+
_load_user_app(app_ref)
|
65
203
|
|
204
|
+
fl = _flow_by_name(flow_ref)
|
205
|
+
console = Console(no_color=not color)
|
206
|
+
console.print(fl._render_spec(verbose=verbose))
|
66
207
|
console.print()
|
67
208
|
table = Table(
|
68
|
-
title=f"Schema for Flow: {
|
69
|
-
|
209
|
+
title=f"Schema for Flow: {fl.name}",
|
210
|
+
title_style="cyan",
|
70
211
|
header_style="bold magenta"
|
71
212
|
)
|
72
213
|
table.add_column("Field", style="cyan")
|
73
214
|
table.add_column("Type", style="green")
|
74
215
|
table.add_column("Attributes", style="yellow")
|
75
|
-
|
76
|
-
for field_name, field_type, attr_str in flow._get_schema():
|
216
|
+
for field_name, field_type, attr_str in fl._get_schema():
|
77
217
|
table.add_row(field_name, field_type, attr_str)
|
78
|
-
|
79
218
|
console.print(table)
|
80
219
|
|
81
220
|
@cli.command()
|
82
|
-
|
221
|
+
@click.argument("app_target", type=str)
|
222
|
+
def setup(app_target: str):
|
83
223
|
"""
|
84
224
|
Check and apply backend setup changes for flows, including the internal and target storage
|
85
225
|
(to export).
|
226
|
+
|
227
|
+
APP_TARGET: path/to/app.py or installed_module.
|
86
228
|
"""
|
229
|
+
app_ref = _get_app_ref_from_specifier(app_target)
|
230
|
+
_load_user_app(app_ref)
|
231
|
+
|
87
232
|
setup_status = sync_setup()
|
88
233
|
click.echo(setup_status)
|
89
234
|
if setup_status.is_up_to_date():
|
@@ -94,80 +239,125 @@ def setup():
|
|
94
239
|
return
|
95
240
|
apply_setup_changes(setup_status)
|
96
241
|
|
97
|
-
@cli.command()
|
242
|
+
@cli.command("drop")
|
243
|
+
@click.argument("app_target", type=str, required=False)
|
98
244
|
@click.argument("flow_name", type=str, nargs=-1)
|
99
245
|
@click.option(
|
100
246
|
"-a", "--all", "drop_all", is_flag=True, show_default=True, default=False,
|
101
247
|
help="Drop the backend setup for all flows with persisted setup, "
|
102
|
-
"even if not defined in the current process."
|
103
|
-
|
248
|
+
"even if not defined in the current process."
|
249
|
+
"If used, APP_TARGET and any listed flow names are ignored.")
|
250
|
+
def drop(app_target: str | None, flow_name: tuple[str, ...], drop_all: bool):
|
104
251
|
"""
|
105
|
-
Drop the backend setup for
|
106
|
-
|
252
|
+
Drop the backend setup for flows.
|
253
|
+
|
254
|
+
Modes of operation:\n
|
255
|
+
1. Drop ALL persisted setups: `cocoindex drop --all`\n
|
256
|
+
2. Drop all flows defined in an app: `cocoindex drop <APP_TARGET>`\n
|
257
|
+
3. Drop specific named flows: `cocoindex drop <APP_TARGET> [FLOW_NAME...]`
|
107
258
|
"""
|
259
|
+
app_ref = None
|
260
|
+
flow_names = []
|
261
|
+
|
108
262
|
if drop_all:
|
263
|
+
if app_target or flow_name:
|
264
|
+
click.echo("Warning: When --all is used, APP_TARGET and any individual flow names are ignored.", err=True)
|
109
265
|
flow_names = flow_names_with_setup()
|
110
|
-
elif
|
111
|
-
|
266
|
+
elif app_target:
|
267
|
+
app_ref = _get_app_ref_from_specifier(app_target)
|
268
|
+
_load_user_app(app_ref)
|
269
|
+
if flow_name:
|
270
|
+
flow_names = list(flow_name)
|
271
|
+
click.echo(f"Preparing to drop specified flows: {', '.join(flow_names)} (in '{app_ref}').", err=True)
|
272
|
+
else:
|
273
|
+
flow_names = [fl.name for fl in flow.flows()]
|
274
|
+
if not flow_names:
|
275
|
+
click.echo(f"No flows found defined in '{app_ref}' to drop.")
|
276
|
+
return
|
277
|
+
click.echo(f"Preparing to drop all flows defined in '{app_ref}': {', '.join(flow_names)}.", err=True)
|
112
278
|
else:
|
113
|
-
|
279
|
+
raise click.UsageError(
|
280
|
+
"Missing arguments. You must either provide an APP_TARGET (to target app-specific flows) "
|
281
|
+
"or use the --all flag."
|
282
|
+
)
|
283
|
+
|
284
|
+
if not flow_names:
|
285
|
+
click.echo("No flows identified for the drop operation.")
|
286
|
+
return
|
287
|
+
|
114
288
|
setup_status = drop_setup(flow_names)
|
115
289
|
click.echo(setup_status)
|
116
290
|
if setup_status.is_up_to_date():
|
117
291
|
click.echo("No flows need to be dropped.")
|
118
292
|
return
|
119
293
|
if not click.confirm(
|
120
|
-
"
|
294
|
+
f"\nThis will apply changes to drop setup for: {', '.join(flow_names)}. Continue? [yes/N]",
|
295
|
+
default=False, show_default=False):
|
296
|
+
click.echo("Drop operation aborted by user.")
|
121
297
|
return
|
122
298
|
apply_setup_changes(setup_status)
|
123
299
|
|
124
300
|
@cli.command()
|
125
|
-
@click.argument("
|
301
|
+
@click.argument("app_flow_specifier", type=str)
|
126
302
|
@click.option(
|
127
303
|
"-L", "--live", is_flag=True, show_default=True, default=False,
|
128
304
|
help="Continuously watch changes from data sources and apply to the target index.")
|
129
305
|
@click.option(
|
130
306
|
"-q", "--quiet", is_flag=True, show_default=True, default=False,
|
131
307
|
help="Avoid printing anything to the standard output, e.g. statistics.")
|
132
|
-
def update(
|
308
|
+
def update(app_flow_specifier: str, live: bool, quiet: bool):
|
133
309
|
"""
|
134
310
|
Update the index to reflect the latest data from data sources.
|
311
|
+
|
312
|
+
APP_FLOW_SPECIFIER: path/to/app.py, module, path/to/app.py:FlowName, or module:FlowName.
|
313
|
+
If :FlowName is omitted, updates all flows.
|
135
314
|
"""
|
315
|
+
app_ref, flow_ref = _parse_app_flow_specifier(app_flow_specifier)
|
316
|
+
_load_user_app(app_ref)
|
317
|
+
|
136
318
|
options = flow.FlowLiveUpdaterOptions(live_mode=live, print_stats=not quiet)
|
137
|
-
if
|
319
|
+
if flow_ref is None:
|
138
320
|
return flow.update_all_flows(options)
|
139
321
|
else:
|
140
|
-
with flow.FlowLiveUpdater(_flow_by_name(
|
322
|
+
with flow.FlowLiveUpdater(_flow_by_name(flow_ref), options) as updater:
|
141
323
|
updater.wait()
|
142
324
|
return updater.update_stats()
|
143
325
|
|
144
326
|
@cli.command()
|
145
|
-
@click.argument("
|
327
|
+
@click.argument("app_flow_specifier", type=str)
|
146
328
|
@click.option(
|
147
329
|
"-o", "--output-dir", type=str, required=False,
|
148
330
|
help="The directory to dump the output to.")
|
149
331
|
@click.option(
|
150
332
|
"--cache/--no-cache", is_flag=True, show_default=True, default=True,
|
151
|
-
help="Use already-cached intermediate data if available.
|
152
|
-
|
153
|
-
"even if it's turned on.")
|
154
|
-
def evaluate(flow_name: str | None, output_dir: str | None, cache: bool = True):
|
333
|
+
help="Use already-cached intermediate data if available.")
|
334
|
+
def evaluate(app_flow_specifier: str, output_dir: str | None, cache: bool = True):
|
155
335
|
"""
|
156
336
|
Evaluate the flow and dump flow outputs to files.
|
157
337
|
|
158
338
|
Instead of updating the index, it dumps what should be indexed to files.
|
159
339
|
Mainly used for evaluation purpose.
|
340
|
+
|
341
|
+
APP_FLOW_SPECIFIER: Specifies the application and optionally the target flow.
|
342
|
+
Can be one of the following formats:\n
|
343
|
+
- path/to/your_app.py\n
|
344
|
+
- an_installed.module_name\n
|
345
|
+
- path/to/your_app.py:SpecificFlowName\n
|
346
|
+
- an_installed.module_name:SpecificFlowName
|
347
|
+
|
348
|
+
:SpecificFlowName can be omitted only if the application defines a single flow.
|
160
349
|
"""
|
161
|
-
|
350
|
+
app_ref, flow_ref = _parse_app_flow_specifier(app_flow_specifier)
|
351
|
+
_load_user_app(app_ref)
|
352
|
+
|
353
|
+
fl = _flow_by_name(flow_ref)
|
162
354
|
if output_dir is None:
|
163
355
|
output_dir = f"eval_{setting.get_app_namespace(trailing_delimiter='_')}{flow_name}_{datetime.datetime.now().strftime('%y%m%d_%H%M%S')}"
|
164
356
|
options = flow.EvaluateAndDumpOptions(output_dir=output_dir, use_cache=cache)
|
165
357
|
fl.evaluate_and_dump(options)
|
166
358
|
|
167
|
-
# Create ServerSettings lazily upon first call, as environment variables may be loaded from files, etc.
|
168
|
-
COCOINDEX_HOST = 'https://cocoindex.io'
|
169
|
-
|
170
359
|
@cli.command()
|
360
|
+
@click.argument("app_target", type=str)
|
171
361
|
@click.option(
|
172
362
|
"-a", "--address", type=str,
|
173
363
|
help="The address to bind the server to, in the format of IP:PORT. "
|
@@ -190,13 +380,18 @@ COCOINDEX_HOST = 'https://cocoindex.io'
|
|
190
380
|
@click.option(
|
191
381
|
"-q", "--quiet", is_flag=True, show_default=True, default=False,
|
192
382
|
help="Avoid printing anything to the standard output, e.g. statistics.")
|
193
|
-
def server(address: str | None, live_update: bool, quiet: bool,
|
194
|
-
cors_cocoindex: bool, cors_local: int | None):
|
383
|
+
def server(app_target: str, address: str | None, live_update: bool, quiet: bool,
|
384
|
+
cors_origin: str | None, cors_cocoindex: bool, cors_local: int | None):
|
195
385
|
"""
|
196
386
|
Start a HTTP server providing REST APIs.
|
197
387
|
|
198
388
|
It will allow tools like CocoInsight to access the server.
|
389
|
+
|
390
|
+
APP_TARGET: path/to/app.py or installed_module.
|
199
391
|
"""
|
392
|
+
app_ref = _get_app_ref_from_specifier(app_target)
|
393
|
+
_load_user_app(app_ref)
|
394
|
+
|
200
395
|
server_settings = setting.ServerSettings.from_env()
|
201
396
|
cors_origins: set[str] = set(server_settings.cors_origins or [])
|
202
397
|
if cors_origin is not None:
|
@@ -223,16 +418,20 @@ def server(address: str | None, live_update: bool, quiet: bool, cors_origin: str
|
|
223
418
|
|
224
419
|
def _flow_name(name: str | None) -> str:
|
225
420
|
names = flow.flow_names()
|
421
|
+
available = ', '.join(sorted(names))
|
226
422
|
if name is not None:
|
227
423
|
if name not in names:
|
228
|
-
raise click.BadParameter(f"Flow {name} not found")
|
424
|
+
raise click.BadParameter(f"Flow '{name}' not found.\nAvailable: {available if names else 'None'}")
|
229
425
|
return name
|
230
426
|
if len(names) == 0:
|
231
|
-
raise click.UsageError("No flows available")
|
427
|
+
raise click.UsageError("No flows available in the loaded application.")
|
232
428
|
elif len(names) == 1:
|
233
429
|
return names[0]
|
234
430
|
else:
|
235
|
-
raise click.UsageError("Multiple flows available, please specify
|
431
|
+
raise click.UsageError(f"Multiple flows available, please specify which flow to target by appending :FlowName to the APP_TARGET.\nAvailable: {available}")
|
236
432
|
|
237
433
|
def _flow_by_name(name: str | None) -> flow.Flow:
|
238
434
|
return flow.flow_by_name(_flow_name(name))
|
435
|
+
|
436
|
+
if __name__ == "__main__":
|
437
|
+
cli()
|
cocoindex/flow.py
CHANGED
@@ -489,7 +489,7 @@ class Flow:
|
|
489
489
|
tree = Tree(f"Flow: {self.full_name}", style="cyan")
|
490
490
|
|
491
491
|
def build_tree(label: str, lines: list):
|
492
|
-
node = Tree(label
|
492
|
+
node = Tree(label=label if lines else label + " None", style="cyan")
|
493
493
|
for line in lines:
|
494
494
|
child_node = node.add(Text(line.content, style="yellow"))
|
495
495
|
child_node.children = build_tree("", line.children).children
|
@@ -657,7 +657,7 @@ async def update_all_flows_async(options: FlowLiveUpdaterOptions) -> dict[str, _
|
|
657
657
|
|
658
658
|
def _get_data_slice_annotation_type(data_slice_type: Type[DataSlice[T]]) -> Type[T] | None:
|
659
659
|
type_args = get_args(data_slice_type)
|
660
|
-
if data_slice_type is DataSlice:
|
660
|
+
if data_slice_type is inspect.Parameter.empty or data_slice_type is DataSlice:
|
661
661
|
return None
|
662
662
|
if get_origin(data_slice_type) != DataSlice or len(type_args) != 1:
|
663
663
|
raise ValueError(f"Expect a DataSlice[T] type, but got {data_slice_type}")
|
@@ -718,9 +718,11 @@ class TransformFlow(Generic[T]):
|
|
718
718
|
for (param_name, param), param_type in zip(sig.parameters.items(), self._flow_arg_types):
|
719
719
|
if param.kind not in (inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
720
720
|
inspect.Parameter.KEYWORD_ONLY):
|
721
|
-
raise ValueError(f"Parameter {param_name} is not a parameter can be passed by name")
|
722
|
-
|
723
|
-
|
721
|
+
raise ValueError(f"Parameter `{param_name}` is not a parameter can be passed by name")
|
722
|
+
encoded_type = encode_enriched_type(param_type)
|
723
|
+
if encoded_type is None:
|
724
|
+
raise ValueError(f"Parameter `{param_name}` has no type annotation")
|
725
|
+
engine_ds = flow_builder_state.engine_flow_builder.add_direct_input(param_name, encoded_type)
|
724
726
|
kwargs[param_name] = DataSlice(_DataSliceState(flow_builder_state, engine_ds))
|
725
727
|
|
726
728
|
output = self._flow_fn(**kwargs)
|
@@ -780,8 +782,13 @@ def transform_flow() -> Callable[[Callable[..., DataSlice[T]]], TransformFlow[T]
|
|
780
782
|
for (param_name, param) in sig.parameters.items():
|
781
783
|
if param.kind not in (inspect.Parameter.POSITIONAL_OR_KEYWORD,
|
782
784
|
inspect.Parameter.KEYWORD_ONLY):
|
783
|
-
raise ValueError(f"Parameter {param_name} is not a parameter can be passed by name")
|
784
|
-
|
785
|
+
raise ValueError(f"Parameter `{param_name}` is not a parameter can be passed by name")
|
786
|
+
value_type_annotation = _get_data_slice_annotation_type(param.annotation)
|
787
|
+
if value_type_annotation is None:
|
788
|
+
raise ValueError(
|
789
|
+
f"Parameter `{param_name}` for {fn} has no value type annotation. "
|
790
|
+
"Please use `cocoindex.DataSlice[T]` where T is the type of the value.")
|
791
|
+
arg_types.append(value_type_annotation)
|
785
792
|
|
786
793
|
_transform_flow = TransformFlow(fn, arg_types)
|
787
794
|
functools.update_wrapper(_transform_flow, fn)
|
cocoindex/lib.py
CHANGED
@@ -1,19 +1,20 @@
|
|
1
1
|
"""
|
2
2
|
Library level functions and states.
|
3
3
|
"""
|
4
|
-
import
|
5
|
-
import
|
6
|
-
import inspect
|
4
|
+
import warnings
|
5
|
+
from typing import Callable, Any
|
7
6
|
|
8
|
-
from
|
9
|
-
|
10
|
-
from . import _engine
|
11
|
-
from . import flow, query, cli, setting
|
7
|
+
from . import _engine, flow, query, setting
|
12
8
|
from .convert import dump_engine_object
|
13
9
|
|
14
10
|
|
15
|
-
def init(settings: setting.Settings):
|
16
|
-
"""
|
11
|
+
def init(settings: setting.Settings | None = None):
|
12
|
+
"""
|
13
|
+
Initialize the cocoindex library.
|
14
|
+
|
15
|
+
If the settings are not provided, they are loaded from the environment variables.
|
16
|
+
"""
|
17
|
+
settings = settings or setting.Settings.from_env()
|
17
18
|
_engine.init(dump_engine_object(settings))
|
18
19
|
setting.set_app_namespace(settings.app_namespace)
|
19
20
|
|
@@ -29,50 +30,42 @@ def stop():
|
|
29
30
|
_engine.stop()
|
30
31
|
|
31
32
|
def main_fn(
|
32
|
-
settings:
|
33
|
-
cocoindex_cmd: str =
|
33
|
+
settings: Any | None = None,
|
34
|
+
cocoindex_cmd: str | None = None,
|
34
35
|
) -> Callable[[Callable], Callable]:
|
35
36
|
"""
|
36
|
-
|
37
|
-
|
37
|
+
DEPRECATED: The @cocoindex.main_fn() decorator is obsolete and has no effect.
|
38
|
+
It will be removed in a future version, which will cause an AttributeError.
|
38
39
|
|
39
|
-
|
40
|
+
Please remove this decorator from your code and use the standalone 'cocoindex' CLI.
|
41
|
+
See the updated CLI usage examples in the warning message.
|
40
42
|
"""
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
43
|
+
warnings.warn(
|
44
|
+
"\n\n"
|
45
|
+
"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n"
|
46
|
+
"CRITICAL DEPRECATION NOTICE from CocoIndex:\n"
|
47
|
+
"The @cocoindex.main_fn() decorator in your script is DEPRECATED and IGNORED.\n"
|
48
|
+
"It provides NO functionality and will be REMOVED entirely in a future version.\n"
|
49
|
+
"If not removed, your script will FAIL with an AttributeError in the future.\n\n"
|
50
|
+
"ACTION REQUIRED: Please REMOVE @cocoindex.main_fn() from your Python script.\n\n"
|
51
|
+
"To use CocoIndex, invoke the standalone 'cocoindex' CLI."
|
52
|
+
" Examples of new CLI usage:\n\n"
|
53
|
+
" To list flows from 'main.py' (previously 'python main.py cocoindex ls'):\n"
|
54
|
+
" cocoindex ls main.py\n\n"
|
55
|
+
" To list all persisted flows (previously 'python main.py cocoindex ls --all'):\n"
|
56
|
+
" cocoindex ls\n\n"
|
57
|
+
" To show 'MyFlow' defined in 'main.py' (previously 'python main.py cocoindex show MyFlow'):\n"
|
58
|
+
" cocoindex show main.py:MyFlow\n\n"
|
59
|
+
" To update all flows in 'my_package.flows_module':\n"
|
60
|
+
" cocoindex update my_package.flows_module\n\n"
|
61
|
+
" To update 'SpecificFlow' in 'my_package.flows_module':\n"
|
62
|
+
" cocoindex update my_package.flows_module:SpecificFlow\n\n"
|
63
|
+
"See cocoindex <command> --help for more details.\n"
|
64
|
+
"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n\n",
|
65
|
+
DeprecationWarning,
|
66
|
+
stacklevel=2
|
67
|
+
)
|
51
68
|
|
52
69
|
def _main_wrapper(fn: Callable) -> Callable:
|
53
|
-
|
54
|
-
@functools.wraps(fn)
|
55
|
-
async def _inner(*args, **kwargs):
|
56
|
-
_pre_init()
|
57
|
-
try:
|
58
|
-
if _should_run_cli():
|
59
|
-
# Schedule to a separate thread as it invokes nested event loop.
|
60
|
-
# return await asyncio.to_thread(_run_cli)
|
61
|
-
return _run_cli()
|
62
|
-
return await fn(*args, **kwargs)
|
63
|
-
finally:
|
64
|
-
stop()
|
65
|
-
return _inner
|
66
|
-
else:
|
67
|
-
@functools.wraps(fn)
|
68
|
-
def _inner(*args, **kwargs):
|
69
|
-
_pre_init()
|
70
|
-
try:
|
71
|
-
if _should_run_cli():
|
72
|
-
return _run_cli()
|
73
|
-
return fn(*args, **kwargs)
|
74
|
-
finally:
|
75
|
-
stop()
|
76
|
-
return _inner
|
77
|
-
|
70
|
+
return fn
|
78
71
|
return _main_wrapper
|
cocoindex/typing.py
CHANGED
@@ -1,9 +1,10 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: cocoindex
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.42
|
4
4
|
Requires-Dist: sentence-transformers>=3.3.1
|
5
5
|
Requires-Dist: click>=8.1.8
|
6
6
|
Requires-Dist: rich>=14.0.0
|
7
|
+
Requires-Dist: python-dotenv>=1.1.0
|
7
8
|
Requires-Dist: pytest ; extra == 'test'
|
8
9
|
Provides-Extra: test
|
9
10
|
License-File: LICENSE
|
@@ -1,15 +1,16 @@
|
|
1
|
-
cocoindex-0.1.
|
2
|
-
cocoindex-0.1.
|
3
|
-
cocoindex-0.1.
|
4
|
-
cocoindex/
|
5
|
-
cocoindex/
|
1
|
+
cocoindex-0.1.42.dist-info/METADATA,sha256=-ukEQ2NadNRsjsH5WOnIQ4RuPN8oDBlLc2DJ4NyLilg,9826
|
2
|
+
cocoindex-0.1.42.dist-info/WHEEL,sha256=UPfJ7S-gMCqCJ6cj5sliE010L87pWgiShNoxmys5TN4,104
|
3
|
+
cocoindex-0.1.42.dist-info/entry_points.txt,sha256=_NretjYVzBdNTn7dK-zgwr7YfG2afz1u1uSE-5bZXF8,46
|
4
|
+
cocoindex-0.1.42.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
5
|
+
cocoindex/__init__.py,sha256=0iO3lM8w35zhVDPsvCGBAXwUQZsCWRRqjt2wFdtJhME,834
|
6
|
+
cocoindex/_engine.cpython-313-darwin.so,sha256=3J5U0Twf2TYRTTyXAHc5ELPlxHgaZw-7mUCy5yAOJNs,56872480
|
6
7
|
cocoindex/auth_registry.py,sha256=NsALZ3SKsDG9cPdrlTlalIqUvgbgFOaFGAbWJNedtJE,692
|
7
|
-
cocoindex/cli.py,sha256=
|
8
|
+
cocoindex/cli.py,sha256=1FvpeLjLHSu4EC3Ds7eeSDjBNTtAc06nsuORkNjv_NQ,16569
|
8
9
|
cocoindex/convert.py,sha256=75HSBie7DokM0RJyUBqeveZRl5y_Fl8lzByoRF0yb2M,6915
|
9
|
-
cocoindex/flow.py,sha256=
|
10
|
+
cocoindex/flow.py,sha256=0otFFqO8XVv-uc1Qbya9BBCK1orv7CGI52TiB5v63xk,28409
|
10
11
|
cocoindex/functions.py,sha256=F79dNmGE127LaU67kF5Oqtf_tIzebFQH7MkyceMX4-s,1830
|
11
12
|
cocoindex/index.py,sha256=LssEOuZi6AqhwKtZM3QFeQpa9T-0ELi8G5DsrYKECvc,534
|
12
|
-
cocoindex/lib.py,sha256=
|
13
|
+
cocoindex/lib.py,sha256=JiLIV5Fi-FNKMlH-6JMoe-UQsvirnFkkOLcoEQk9WkE,2903
|
13
14
|
cocoindex/llm.py,sha256=_3rtahuKcqcEHPkFSwhXOSrekZyGxVApPoYtlU_chcA,348
|
14
15
|
cocoindex/op.py,sha256=yyB3gYYj6uIeoGW9FXuj9Ludaz50QYDeqGgi3dKG1_I,10739
|
15
16
|
cocoindex/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
@@ -21,6 +22,6 @@ cocoindex/sources.py,sha256=7lpwYLsFCRfbURKf79Vu0JZZoXjAYY0DxNHzUb-VHBY,1327
|
|
21
22
|
cocoindex/storages.py,sha256=MFMsfyOCYMggTWeWrOi82miqOXQmiUuqq828x5htBr0,2207
|
22
23
|
cocoindex/tests/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
|
23
24
|
cocoindex/tests/test_convert.py,sha256=7jc--I3frrg7DB5MPr4JFzE7DSCznJuWyHdlDLQJ_fM,15516
|
24
|
-
cocoindex/typing.py,sha256=
|
25
|
+
cocoindex/typing.py,sha256=RMJXdey5_vd7JbPTEdUw1DeQ4Uu4MicdiaXjzQHFNRA,9031
|
25
26
|
cocoindex/utils.py,sha256=eClhMdjBjcXaDkah-rPUmE7Y5Ncd7S1goUe2qTETR08,456
|
26
|
-
cocoindex-0.1.
|
27
|
+
cocoindex-0.1.42.dist-info/RECORD,,
|
File without changes
|
File without changes
|