cocoindex 0.1.41__cp311-cp311-macosx_11_0_arm64.whl → 0.1.43__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/__init__.py +1 -1
- cocoindex/_engine.cpython-311-darwin.so +0 -0
- cocoindex/cli.py +271 -70
- cocoindex/flow.py +2 -2
- cocoindex/lib.py +42 -49
- cocoindex/typing.py +2 -0
- {cocoindex-0.1.41.dist-info → cocoindex-0.1.43.dist-info}/METADATA +3 -2
- {cocoindex-0.1.41.dist-info → cocoindex-0.1.43.dist-info}/RECORD +11 -10
- cocoindex-0.1.43.dist-info/entry_points.txt +2 -0
- {cocoindex-0.1.41.dist-info → cocoindex-0.1.43.dist-info}/WHEEL +0 -0
- {cocoindex-0.1.41.dist-info → cocoindex-0.1.43.dist-info}/licenses/LICENSE +0 -0
cocoindex/__init__.py
CHANGED
@@ -5,7 +5,7 @@ from . import functions, query, sources, storages, cli, utils
|
|
5
5
|
|
6
6
|
from .auth_registry import AuthEntryReference, add_auth_entry, ref_auth_entry
|
7
7
|
from .flow import FlowBuilder, DataScope, DataSlice, Flow, transform_flow
|
8
|
-
from .flow import flow_def
|
8
|
+
from .flow import flow_def
|
9
9
|
from .flow import EvaluateAndDumpOptions, GeneratedField
|
10
10
|
from .flow import update_all_flows_async, FlowLiveUpdater, FlowLiveUpdaterOptions
|
11
11
|
from .lib import init, start_server, stop, main_fn
|
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,127 @@ 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
|
+
\b
|
255
|
+
Modes of operation:
|
256
|
+
1. Drop ALL persisted setups: `cocoindex drop --all`
|
257
|
+
2. Drop all flows defined in an app: `cocoindex drop <APP_TARGET>`
|
258
|
+
3. Drop specific named flows: `cocoindex drop <APP_TARGET> [FLOW_NAME...]`
|
107
259
|
"""
|
260
|
+
app_ref = None
|
261
|
+
flow_names = []
|
262
|
+
|
108
263
|
if drop_all:
|
264
|
+
if app_target or flow_name:
|
265
|
+
click.echo("Warning: When --all is used, APP_TARGET and any individual flow names are ignored.", err=True)
|
109
266
|
flow_names = flow_names_with_setup()
|
110
|
-
elif
|
111
|
-
|
267
|
+
elif app_target:
|
268
|
+
app_ref = _get_app_ref_from_specifier(app_target)
|
269
|
+
_load_user_app(app_ref)
|
270
|
+
if flow_name:
|
271
|
+
flow_names = list(flow_name)
|
272
|
+
click.echo(f"Preparing to drop specified flows: {', '.join(flow_names)} (in '{app_ref}').", err=True)
|
273
|
+
else:
|
274
|
+
flow_names = flow.flow_names()
|
275
|
+
if not flow_names:
|
276
|
+
click.echo(f"No flows found defined in '{app_ref}' to drop.")
|
277
|
+
return
|
278
|
+
click.echo(f"Preparing to drop all flows defined in '{app_ref}': {', '.join(flow_names)}.", err=True)
|
112
279
|
else:
|
113
|
-
|
280
|
+
raise click.UsageError(
|
281
|
+
"Missing arguments. You must either provide an APP_TARGET (to target app-specific flows) "
|
282
|
+
"or use the --all flag."
|
283
|
+
)
|
284
|
+
|
285
|
+
if not flow_names:
|
286
|
+
click.echo("No flows identified for the drop operation.")
|
287
|
+
return
|
288
|
+
|
114
289
|
setup_status = drop_setup(flow_names)
|
115
290
|
click.echo(setup_status)
|
116
291
|
if setup_status.is_up_to_date():
|
117
292
|
click.echo("No flows need to be dropped.")
|
118
293
|
return
|
119
294
|
if not click.confirm(
|
120
|
-
"
|
295
|
+
f"\nThis will apply changes to drop setup for: {', '.join(flow_names)}. Continue? [yes/N]",
|
296
|
+
default=False, show_default=False):
|
297
|
+
click.echo("Drop operation aborted by user.")
|
121
298
|
return
|
122
299
|
apply_setup_changes(setup_status)
|
123
300
|
|
124
301
|
@cli.command()
|
125
|
-
@click.argument("
|
302
|
+
@click.argument("app_flow_specifier", type=str)
|
126
303
|
@click.option(
|
127
304
|
"-L", "--live", is_flag=True, show_default=True, default=False,
|
128
305
|
help="Continuously watch changes from data sources and apply to the target index.")
|
129
306
|
@click.option(
|
130
307
|
"-q", "--quiet", is_flag=True, show_default=True, default=False,
|
131
308
|
help="Avoid printing anything to the standard output, e.g. statistics.")
|
132
|
-
def update(
|
309
|
+
def update(app_flow_specifier: str, live: bool, quiet: bool):
|
133
310
|
"""
|
134
311
|
Update the index to reflect the latest data from data sources.
|
312
|
+
|
313
|
+
APP_FLOW_SPECIFIER: path/to/app.py, module, path/to/app.py:FlowName, or module:FlowName.
|
314
|
+
If :FlowName is omitted, updates all flows.
|
135
315
|
"""
|
316
|
+
app_ref, flow_ref = _parse_app_flow_specifier(app_flow_specifier)
|
317
|
+
_load_user_app(app_ref)
|
318
|
+
|
136
319
|
options = flow.FlowLiveUpdaterOptions(live_mode=live, print_stats=not quiet)
|
137
|
-
if
|
320
|
+
if flow_ref is None:
|
138
321
|
return flow.update_all_flows(options)
|
139
322
|
else:
|
140
|
-
with flow.FlowLiveUpdater(_flow_by_name(
|
323
|
+
with flow.FlowLiveUpdater(_flow_by_name(flow_ref), options) as updater:
|
141
324
|
updater.wait()
|
142
325
|
return updater.update_stats()
|
143
326
|
|
144
327
|
@cli.command()
|
145
|
-
@click.argument("
|
328
|
+
@click.argument("app_flow_specifier", type=str)
|
146
329
|
@click.option(
|
147
330
|
"-o", "--output-dir", type=str, required=False,
|
148
331
|
help="The directory to dump the output to.")
|
149
332
|
@click.option(
|
150
333
|
"--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):
|
334
|
+
help="Use already-cached intermediate data if available.")
|
335
|
+
def evaluate(app_flow_specifier: str, output_dir: str | None, cache: bool = True):
|
155
336
|
"""
|
156
337
|
Evaluate the flow and dump flow outputs to files.
|
157
338
|
|
158
339
|
Instead of updating the index, it dumps what should be indexed to files.
|
159
340
|
Mainly used for evaluation purpose.
|
341
|
+
|
342
|
+
\b
|
343
|
+
APP_FLOW_SPECIFIER: Specifies the application and optionally the target flow.
|
344
|
+
Can be one of the following formats:
|
345
|
+
- path/to/your_app.py
|
346
|
+
- an_installed.module_name
|
347
|
+
- path/to/your_app.py:SpecificFlowName
|
348
|
+
- an_installed.module_name:SpecificFlowName
|
349
|
+
|
350
|
+
:SpecificFlowName can be omitted only if the application defines a single flow.
|
160
351
|
"""
|
161
|
-
|
352
|
+
app_ref, flow_ref = _parse_app_flow_specifier(app_flow_specifier)
|
353
|
+
_load_user_app(app_ref)
|
354
|
+
|
355
|
+
fl = _flow_by_name(flow_ref)
|
162
356
|
if output_dir is None:
|
163
357
|
output_dir = f"eval_{setting.get_app_namespace(trailing_delimiter='_')}{flow_name}_{datetime.datetime.now().strftime('%y%m%d_%H%M%S')}"
|
164
358
|
options = flow.EvaluateAndDumpOptions(output_dir=output_dir, use_cache=cache)
|
165
359
|
fl.evaluate_and_dump(options)
|
166
360
|
|
167
|
-
# Create ServerSettings lazily upon first call, as environment variables may be loaded from files, etc.
|
168
|
-
COCOINDEX_HOST = 'https://cocoindex.io'
|
169
|
-
|
170
361
|
@cli.command()
|
362
|
+
@click.argument("app_target", type=str)
|
171
363
|
@click.option(
|
172
364
|
"-a", "--address", type=str,
|
173
365
|
help="The address to bind the server to, in the format of IP:PORT. "
|
@@ -190,13 +382,18 @@ COCOINDEX_HOST = 'https://cocoindex.io'
|
|
190
382
|
@click.option(
|
191
383
|
"-q", "--quiet", is_flag=True, show_default=True, default=False,
|
192
384
|
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):
|
385
|
+
def server(app_target: str, address: str | None, live_update: bool, quiet: bool,
|
386
|
+
cors_origin: str | None, cors_cocoindex: bool, cors_local: int | None):
|
195
387
|
"""
|
196
388
|
Start a HTTP server providing REST APIs.
|
197
389
|
|
198
390
|
It will allow tools like CocoInsight to access the server.
|
391
|
+
|
392
|
+
APP_TARGET: path/to/app.py or installed_module.
|
199
393
|
"""
|
394
|
+
app_ref = _get_app_ref_from_specifier(app_target)
|
395
|
+
_load_user_app(app_ref)
|
396
|
+
|
200
397
|
server_settings = setting.ServerSettings.from_env()
|
201
398
|
cors_origins: set[str] = set(server_settings.cors_origins or [])
|
202
399
|
if cors_origin is not None:
|
@@ -223,16 +420,20 @@ def server(address: str | None, live_update: bool, quiet: bool, cors_origin: str
|
|
223
420
|
|
224
421
|
def _flow_name(name: str | None) -> str:
|
225
422
|
names = flow.flow_names()
|
423
|
+
available = ', '.join(sorted(names))
|
226
424
|
if name is not None:
|
227
425
|
if name not in names:
|
228
|
-
raise click.BadParameter(f"Flow {name} not found")
|
426
|
+
raise click.BadParameter(f"Flow '{name}' not found.\nAvailable: {available if names else 'None'}")
|
229
427
|
return name
|
230
428
|
if len(names) == 0:
|
231
|
-
raise click.UsageError("No flows available")
|
429
|
+
raise click.UsageError("No flows available in the loaded application.")
|
232
430
|
elif len(names) == 1:
|
233
431
|
return names[0]
|
234
432
|
else:
|
235
|
-
raise click.UsageError("Multiple flows available, please specify
|
433
|
+
raise click.UsageError(f"Multiple flows available, please specify which flow to target by appending :FlowName to the APP_TARGET.\nAvailable: {available}")
|
236
434
|
|
237
435
|
def _flow_by_name(name: str | None) -> flow.Flow:
|
238
436
|
return flow.flow_by_name(_flow_name(name))
|
437
|
+
|
438
|
+
if __name__ == "__main__":
|
439
|
+
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}")
|
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.43
|
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
|
@@ -154,7 +155,7 @@ It defines an index flow like this:
|
|
154
155
|
| [Embeddings to Qdrant](examples/text_embedding_qdrant) | Index documents in a Qdrant collection for semantic search |
|
155
156
|
| [FastAPI Server with Docker](examples/fastapi_server_docker) | Run the semantic search server in a Dockerized FastAPI setup |
|
156
157
|
| [Product Recommendation](examples/product_recommendation) | Build real-time product recommendations with LLM and graph database|
|
157
|
-
| [Image Search with Vision API](examples/
|
158
|
+
| [Image Search with Vision API](examples/image_search) | Generates detailed captions for images using a vision model, embeds them, enables live-updating semantic search via FastAPI and served on a React frontend|
|
158
159
|
|
159
160
|
More coming and stay tuned 👀!
|
160
161
|
|
@@ -1,15 +1,16 @@
|
|
1
|
-
cocoindex-0.1.
|
2
|
-
cocoindex-0.1.
|
3
|
-
cocoindex-0.1.
|
4
|
-
cocoindex/
|
5
|
-
cocoindex/
|
1
|
+
cocoindex-0.1.43.dist-info/METADATA,sha256=2n3yADdVbrbv-m-x7qtZX8l8D_2h37MEP2tY5OqTyBo,9818
|
2
|
+
cocoindex-0.1.43.dist-info/WHEEL,sha256=p_tvkyHH2UmMBrR2Gemb1ahXJMM2SXUIsCLrWZgJvB8,104
|
3
|
+
cocoindex-0.1.43.dist-info/entry_points.txt,sha256=_NretjYVzBdNTn7dK-zgwr7YfG2afz1u1uSE-5bZXF8,46
|
4
|
+
cocoindex-0.1.43.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
5
|
+
cocoindex/__init__.py,sha256=ztuMoVHbGsyhgnN_CHBnY4yQtcsHi4O-u-Q3eUFaB5Y,816
|
6
|
+
cocoindex/_engine.cpython-311-darwin.so,sha256=yTewruRgB6wQCP00OjCDNCILMVk0dEInGPHF3jhCrRg,56886416
|
6
7
|
cocoindex/auth_registry.py,sha256=NsALZ3SKsDG9cPdrlTlalIqUvgbgFOaFGAbWJNedtJE,692
|
7
|
-
cocoindex/cli.py,sha256=
|
8
|
+
cocoindex/cli.py,sha256=Gbn6vz37Gi5ckZYdRdJkcbNcokLbtDhH9wrZswISeRc,16554
|
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.43.dist-info/RECORD,,
|
File without changes
|
File without changes
|