indexify 0.2__tar.gz → 0.2.2__tar.gz
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.
- {indexify-0.2 → indexify-0.2.2}/PKG-INFO +1 -1
- {indexify-0.2 → indexify-0.2.2}/indexify/__init__.py +2 -0
- {indexify-0.2 → indexify-0.2.2}/indexify/cli.py +21 -9
- {indexify-0.2 → indexify-0.2.2}/indexify/client.py +2 -2
- {indexify-0.2 → indexify-0.2.2}/indexify/executor/agent.py +11 -9
- {indexify-0.2 → indexify-0.2.2}/indexify/executor/api_objects.py +2 -2
- indexify-0.2.2/indexify/executor/runtime_probes.py +48 -0
- {indexify-0.2 → indexify-0.2.2}/indexify/functions_sdk/graph.py +5 -0
- {indexify-0.2 → indexify-0.2.2}/indexify/functions_sdk/graph_validation.py +18 -21
- {indexify-0.2 → indexify-0.2.2}/indexify/functions_sdk/image.py +10 -2
- {indexify-0.2 → indexify-0.2.2}/indexify/functions_sdk/indexify_functions.py +9 -13
- {indexify-0.2 → indexify-0.2.2}/indexify/functions_sdk/object_serializer.py +0 -1
- {indexify-0.2 → indexify-0.2.2}/indexify/local_client.py +1 -1
- {indexify-0.2 → indexify-0.2.2}/pyproject.toml +1 -1
- {indexify-0.2 → indexify-0.2.2}/LICENSE.txt +0 -0
- {indexify-0.2 → indexify-0.2.2}/README.md +0 -0
- {indexify-0.2 → indexify-0.2.2}/indexify/base_client.py +0 -0
- {indexify-0.2 → indexify-0.2.2}/indexify/data_loaders/__init__.py +0 -0
- {indexify-0.2 → indexify-0.2.2}/indexify/data_loaders/local_directory_loader.py +0 -0
- {indexify-0.2 → indexify-0.2.2}/indexify/data_loaders/url_loader.py +0 -0
- {indexify-0.2 → indexify-0.2.2}/indexify/error.py +0 -0
- {indexify-0.2 → indexify-0.2.2}/indexify/executor/downloader.py +0 -0
- {indexify-0.2 → indexify-0.2.2}/indexify/executor/executor_tasks.py +0 -0
- {indexify-0.2 → indexify-0.2.2}/indexify/executor/function_worker.py +0 -0
- {indexify-0.2 → indexify-0.2.2}/indexify/executor/indexify_executor.py +0 -0
- {indexify-0.2 → indexify-0.2.2}/indexify/executor/task_reporter.py +0 -0
- {indexify-0.2 → indexify-0.2.2}/indexify/executor/task_store.py +0 -0
- {indexify-0.2 → indexify-0.2.2}/indexify/foo +0 -0
- {indexify-0.2 → indexify-0.2.2}/indexify/functions_sdk/data_objects.py +0 -0
- {indexify-0.2 → indexify-0.2.2}/indexify/functions_sdk/local_cache.py +0 -0
- {indexify-0.2 → indexify-0.2.2}/indexify/remote_client.py +0 -0
- {indexify-0.2 → indexify-0.2.2}/indexify/settings.py +0 -0
@@ -1,6 +1,7 @@
|
|
1
1
|
from . import data_loaders
|
2
2
|
from .client import create_client
|
3
3
|
from .functions_sdk.graph import Graph
|
4
|
+
from .functions_sdk.image import Image
|
4
5
|
from .functions_sdk.indexify_functions import (
|
5
6
|
indexify_function,
|
6
7
|
indexify_router,
|
@@ -12,6 +13,7 @@ from .settings import DEFAULT_SERVICE_URL
|
|
12
13
|
__all__ = [
|
13
14
|
"data_loaders",
|
14
15
|
"Graph",
|
16
|
+
"Image",
|
15
17
|
"indexify_function",
|
16
18
|
"indexify_router",
|
17
19
|
"DEFAULT_SERVICE_URL",
|
@@ -19,7 +19,7 @@ from rich.theme import Theme
|
|
19
19
|
|
20
20
|
from indexify.executor.agent import ExtractorAgent
|
21
21
|
from indexify.executor.function_worker import FunctionWorker
|
22
|
-
from indexify.functions_sdk.image import Image
|
22
|
+
from indexify.functions_sdk.image import Image, DEFAULT_IMAGE
|
23
23
|
|
24
24
|
custom_theme = Theme(
|
25
25
|
{
|
@@ -130,7 +130,6 @@ def build_image(workflow_file_path: str, func_names: List[str]):
|
|
130
130
|
)
|
131
131
|
|
132
132
|
found_funcs = []
|
133
|
-
graph = None
|
134
133
|
for name, obj in globals_dict.items():
|
135
134
|
for func_name in func_names:
|
136
135
|
if name == func_name:
|
@@ -143,6 +142,15 @@ def build_image(workflow_file_path: str, func_names: List[str]):
|
|
143
142
|
)
|
144
143
|
|
145
144
|
|
145
|
+
@app.command(help="Build default image for indexify")
|
146
|
+
def build_default_image():
|
147
|
+
_build_image(image=DEFAULT_IMAGE)
|
148
|
+
|
149
|
+
console.print(
|
150
|
+
Text(f"Built default indexify image", style="cyan"),
|
151
|
+
)
|
152
|
+
|
153
|
+
|
146
154
|
@app.command(help="Joins the extractors to the coordinator server")
|
147
155
|
def executor(
|
148
156
|
server_addr: str = "localhost:8900",
|
@@ -211,25 +219,29 @@ def _build_image(image: Image, func_name: str = None):
|
|
211
219
|
)
|
212
220
|
exit(-1)
|
213
221
|
|
214
|
-
|
215
|
-
FROM {
|
222
|
+
docker_file = f"""
|
223
|
+
FROM {image._base_image}
|
224
|
+
|
225
|
+
RUN mkdir -p ~/.indexify
|
226
|
+
|
227
|
+
RUN touch ~/.indexify/image_name
|
228
|
+
|
229
|
+
RUN echo {image._image_name} > ~/.indexify/image_name
|
216
230
|
|
217
231
|
WORKDIR /app
|
218
232
|
|
219
233
|
"""
|
220
234
|
|
221
|
-
docker_file_str = docker_file_str_template.format(base_image=image._base_image)
|
222
|
-
|
223
235
|
run_strs = ["RUN " + i for i in image._run_strs]
|
224
236
|
|
225
|
-
|
237
|
+
docker_file += "\n".join(run_strs)
|
226
238
|
|
227
239
|
console.print("Creating image using Dockerfile contents:", style="cyan bold")
|
228
|
-
console.print(f"{
|
240
|
+
console.print(f"{docker_file}", style="magenta")
|
229
241
|
|
230
242
|
client = docker.from_env()
|
231
243
|
client.images.build(
|
232
|
-
fileobj=io.BytesIO(
|
244
|
+
fileobj=io.BytesIO(docker_file.encode()),
|
233
245
|
tag=f"{image._image_name}:{image._tag}",
|
234
246
|
rm=True,
|
235
247
|
)
|
@@ -9,10 +9,10 @@ from .settings import DEFAULT_SERVICE_URL
|
|
9
9
|
def create_client(
|
10
10
|
service_url: str = DEFAULT_SERVICE_URL,
|
11
11
|
config_path: Optional[str] = None,
|
12
|
-
|
12
|
+
in_process: bool = False,
|
13
13
|
*args,
|
14
14
|
**kwargs,
|
15
15
|
) -> IndexifyClient:
|
16
|
-
if
|
16
|
+
if in_process:
|
17
17
|
return LocalClient()
|
18
18
|
return RemoteClient(config_path=config_path, service_url=service_url, **kwargs)
|
@@ -1,7 +1,6 @@
|
|
1
1
|
import asyncio
|
2
2
|
import json
|
3
3
|
import ssl
|
4
|
-
import traceback
|
5
4
|
from concurrent.futures.process import BrokenProcessPool
|
6
5
|
from typing import Dict, List, Optional
|
7
6
|
|
@@ -24,6 +23,7 @@ from .api_objects import ExecutorMetadata, Task
|
|
24
23
|
from .downloader import DownloadedInputs, Downloader
|
25
24
|
from .executor_tasks import DownloadGraphTask, DownloadInputTask, ExtractTask
|
26
25
|
from .function_worker import FunctionWorker
|
26
|
+
from .runtime_probes import ProbeInfo, RuntimeProbes
|
27
27
|
from .task_reporter import TaskReporter
|
28
28
|
from .task_store import CompletedTask, TaskStore
|
29
29
|
|
@@ -98,6 +98,7 @@ class ExtractorAgent:
|
|
98
98
|
self._task_reporter = TaskReporter(
|
99
99
|
base_url=self._base_url, executor_id=self._executor_id
|
100
100
|
)
|
101
|
+
self._probe = RuntimeProbes()
|
101
102
|
|
102
103
|
async def task_completion_reporter(self):
|
103
104
|
console.print(Text("Starting task completion reporter", style="bold cyan"))
|
@@ -305,11 +306,12 @@ class ExtractorAgent:
|
|
305
306
|
words = snake_str.split("_")
|
306
307
|
return words[0].capitalize() + "" + " ".join(words[1:])
|
307
308
|
|
309
|
+
runtime_probe: ProbeInfo = self._probe.probe()
|
308
310
|
data = ExecutorMetadata(
|
309
311
|
id=self._executor_id,
|
310
|
-
|
311
|
-
|
312
|
-
labels=
|
312
|
+
addr="",
|
313
|
+
image_name=runtime_probe.image_name,
|
314
|
+
labels=runtime_probe.labels,
|
313
315
|
).model_dump()
|
314
316
|
|
315
317
|
panel_content = "\n".join(
|
@@ -318,7 +320,7 @@ class ExtractorAgent:
|
|
318
320
|
console.print(
|
319
321
|
Panel(
|
320
322
|
panel_content,
|
321
|
-
title="
|
323
|
+
title="attempting to Register Executor",
|
322
324
|
border_style="cyan",
|
323
325
|
)
|
324
326
|
)
|
@@ -333,7 +335,7 @@ class ExtractorAgent:
|
|
333
335
|
headers={"Content-Type": "application/json"},
|
334
336
|
) as event_source:
|
335
337
|
console.print(
|
336
|
-
Text("
|
338
|
+
Text("executor registered successfully", style="bold green")
|
337
339
|
)
|
338
340
|
async for sse in event_source.aiter_sse():
|
339
341
|
data = json.loads(sse.data)
|
@@ -345,14 +347,14 @@ class ExtractorAgent:
|
|
345
347
|
self._task_store.add_tasks(tasks)
|
346
348
|
except Exception as e:
|
347
349
|
console.print(
|
348
|
-
Text("
|
349
|
-
+ Text(f"
|
350
|
+
Text("registration Error: ", style="red bold")
|
351
|
+
+ Text(f"failed to register: {e}", style="red")
|
350
352
|
)
|
351
353
|
await asyncio.sleep(5)
|
352
354
|
continue
|
353
355
|
|
354
356
|
async def _shutdown(self, loop):
|
355
|
-
console.print(Text("
|
357
|
+
console.print(Text("shutting down agent...", style="bold yellow"))
|
356
358
|
self._should_run = False
|
357
359
|
for task in asyncio.all_tasks(loop):
|
358
360
|
task.cancel()
|
@@ -0,0 +1,48 @@
|
|
1
|
+
import os
|
2
|
+
import platform
|
3
|
+
import sys
|
4
|
+
from typing import Any, Dict, Tuple
|
5
|
+
|
6
|
+
from pydantic import BaseModel
|
7
|
+
|
8
|
+
|
9
|
+
class ProbeInfo(BaseModel):
|
10
|
+
image_name: str
|
11
|
+
python_major_version: int
|
12
|
+
labels: Dict[str, Any] = {}
|
13
|
+
|
14
|
+
|
15
|
+
class RuntimeProbes:
|
16
|
+
def __init__(self) -> None:
|
17
|
+
self._image_name = self._read_image_name()
|
18
|
+
self._os_name = platform.system()
|
19
|
+
self._architecture = platform.machine()
|
20
|
+
(
|
21
|
+
self._python_version_major,
|
22
|
+
self._python_version_minor,
|
23
|
+
) = self._get_python_version()
|
24
|
+
|
25
|
+
def _read_image_name(self) -> str:
|
26
|
+
file_path = os.path.expanduser("~/.indexify/image_name")
|
27
|
+
if os.path.exists(file_path):
|
28
|
+
with open(file_path, "r") as file:
|
29
|
+
return file.read().strip()
|
30
|
+
return "indexify-executor-default"
|
31
|
+
|
32
|
+
def _get_python_version(self) -> Tuple[int, int]:
|
33
|
+
version_info = sys.version_info
|
34
|
+
return version_info.major, version_info.minor
|
35
|
+
|
36
|
+
def probe(self) -> ProbeInfo:
|
37
|
+
labels = {
|
38
|
+
"os": self._os_name,
|
39
|
+
"image_name": self._image_name,
|
40
|
+
"architecture": self._architecture,
|
41
|
+
"python_major_version": self._python_version_major,
|
42
|
+
"python_minor_version": self._python_version_minor,
|
43
|
+
}
|
44
|
+
return ProbeInfo(
|
45
|
+
image_name=self._image_name,
|
46
|
+
python_major_version=self._python_version_major,
|
47
|
+
labels=labels,
|
48
|
+
)
|
@@ -66,6 +66,7 @@ class FunctionMetadata(BaseModel):
|
|
66
66
|
fn_name: str
|
67
67
|
description: str
|
68
68
|
reducer: bool = False
|
69
|
+
image_name: str
|
69
70
|
payload_encoder: str = "cloudpickle"
|
70
71
|
|
71
72
|
|
@@ -74,6 +75,7 @@ class RouterMetadata(BaseModel):
|
|
74
75
|
description: str
|
75
76
|
source_fn: str
|
76
77
|
target_fns: List[str]
|
78
|
+
image_name: str
|
77
79
|
payload_encoder: str = "cloudpickle"
|
78
80
|
|
79
81
|
|
@@ -244,6 +246,7 @@ class Graph:
|
|
244
246
|
fn_name=start_node.fn_name,
|
245
247
|
description=start_node.description,
|
246
248
|
reducer=start_node.accumulate is not None,
|
249
|
+
image_name=start_node.image._image_name,
|
247
250
|
)
|
248
251
|
metadata_edges = self.edges.copy()
|
249
252
|
metadata_nodes = {}
|
@@ -256,6 +259,7 @@ class Graph:
|
|
256
259
|
source_fn=node_name,
|
257
260
|
target_fns=self.routers[node_name],
|
258
261
|
payload_encoder=node.payload_encoder,
|
262
|
+
image_name=node.image._image_name,
|
259
263
|
)
|
260
264
|
)
|
261
265
|
else:
|
@@ -265,6 +269,7 @@ class Graph:
|
|
265
269
|
fn_name=node.fn_name,
|
266
270
|
description=node.description,
|
267
271
|
reducer=node.accumulate is not None,
|
272
|
+
image_name=node.image._image_name,
|
268
273
|
)
|
269
274
|
)
|
270
275
|
return ComputeGraphMetadata(
|
@@ -42,28 +42,25 @@ def validate_route(
|
|
42
42
|
|
43
43
|
if signature.return_annotation == inspect.Signature.empty:
|
44
44
|
raise Exception(f"Function {from_node.name} has empty return type annotation")
|
45
|
+
|
46
|
+
return_annotation = signature.return_annotation
|
45
47
|
|
46
|
-
|
47
|
-
|
48
|
+
if hasattr(return_annotation, '__origin__') and return_annotation.__origin__ is Union:
|
49
|
+
for arg in return_annotation.__args__:
|
50
|
+
if hasattr(arg, 'name'):
|
51
|
+
if arg not in to_nodes:
|
52
|
+
raise Exception(
|
53
|
+
f"Unable to find {arg.name} in to_nodes {[node.name for node in to_nodes]}"
|
54
|
+
)
|
48
55
|
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
raise Exception(f"Invalid router for {from_node.name}, lte 1 route.")
|
56
|
+
if hasattr(return_annotation, '__origin__') and return_annotation.__origin__ is list:
|
57
|
+
union_args = return_annotation.__args__[0].__args__
|
58
|
+
for arg in union_args:
|
59
|
+
if hasattr(arg, 'name'):
|
60
|
+
if arg not in to_nodes:
|
61
|
+
raise Exception(
|
62
|
+
f"Unable to find {arg.name} in to_nodes {[node.name for node in to_nodes]}"
|
63
|
+
)
|
58
64
|
else:
|
59
|
-
raise Exception(
|
60
|
-
f"Invalid router for {from_node.name}, cannot find output nodes"
|
61
|
-
)
|
65
|
+
raise Exception(f"Return type of {from_node.name} is not a Union")
|
62
66
|
|
63
|
-
to_node_names = [i.name for i in to_nodes]
|
64
|
-
|
65
|
-
for src_node in src_route_nodes:
|
66
|
-
if src_node not in to_node_names:
|
67
|
-
raise Exception(
|
68
|
-
f"Unable to find {src_node} in to_nodes " f"{to_node_names}"
|
69
|
-
)
|
@@ -7,9 +7,8 @@ class Image:
|
|
7
7
|
self._base_image = None
|
8
8
|
|
9
9
|
self._run_strs = []
|
10
|
-
pass
|
11
10
|
|
12
|
-
def
|
11
|
+
def name(self, image_name):
|
13
12
|
self._image_name = image_name
|
14
13
|
return self
|
15
14
|
|
@@ -24,3 +23,12 @@ class Image:
|
|
24
23
|
def run(self, run_str):
|
25
24
|
self._run_strs.append(run_str)
|
26
25
|
return self
|
26
|
+
|
27
|
+
|
28
|
+
DEFAULT_IMAGE = (
|
29
|
+
Image()
|
30
|
+
.name("indexify-executor-default")
|
31
|
+
.base_image("python:3.10.15-slim-bookworm")
|
32
|
+
.tag("latest")
|
33
|
+
.run("pip install indexify")
|
34
|
+
)
|
@@ -16,7 +16,7 @@ from pydantic import BaseModel
|
|
16
16
|
from typing_extensions import get_type_hints
|
17
17
|
|
18
18
|
from .data_objects import IndexifyData, RouterOutput
|
19
|
-
from .image import Image
|
19
|
+
from .image import DEFAULT_IMAGE, Image
|
20
20
|
|
21
21
|
|
22
22
|
class EmbeddingIndexes(BaseModel):
|
@@ -34,8 +34,8 @@ class PlacementConstraints(BaseModel):
|
|
34
34
|
|
35
35
|
class IndexifyFunction(ABC):
|
36
36
|
name: str = ""
|
37
|
-
base_image: Optional[str] = None
|
38
37
|
description: str = ""
|
38
|
+
image: Optional[Image] = DEFAULT_IMAGE
|
39
39
|
placement_constraints: List[PlacementConstraints] = []
|
40
40
|
accumulate: Optional[Type[Any]] = None
|
41
41
|
payload_encoder: Optional[str] = "cloudpickle"
|
@@ -53,7 +53,7 @@ class IndexifyFunction(ABC):
|
|
53
53
|
class IndexifyRouter(ABC):
|
54
54
|
name: str = ""
|
55
55
|
description: str = ""
|
56
|
-
image: Image =
|
56
|
+
image: Optional[Image] = DEFAULT_IMAGE
|
57
57
|
placement_constraints: List[PlacementConstraints] = []
|
58
58
|
payload_encoder: Optional[str] = "cloudpickle"
|
59
59
|
|
@@ -65,9 +65,9 @@ class IndexifyRouter(ABC):
|
|
65
65
|
def indexify_router(
|
66
66
|
name: Optional[str] = None,
|
67
67
|
description: Optional[str] = "",
|
68
|
-
image: Image =
|
68
|
+
image: Optional[Image] = DEFAULT_IMAGE,
|
69
69
|
placement_constraints: List[PlacementConstraints] = [],
|
70
|
-
|
70
|
+
payload_encoder: Optional[str] = "cloudpickle",
|
71
71
|
):
|
72
72
|
def construct(fn):
|
73
73
|
args = locals().copy()
|
@@ -90,7 +90,7 @@ def indexify_router(
|
|
90
90
|
setattr(IndexifyRo, key, value)
|
91
91
|
|
92
92
|
IndexifyRo.image = image
|
93
|
-
IndexifyRo.payload_encoder =
|
93
|
+
IndexifyRo.payload_encoder = payload_encoder
|
94
94
|
return IndexifyRo
|
95
95
|
|
96
96
|
return construct
|
@@ -99,11 +99,9 @@ def indexify_router(
|
|
99
99
|
def indexify_function(
|
100
100
|
name: Optional[str] = None,
|
101
101
|
description: Optional[str] = "",
|
102
|
-
image: Image =
|
102
|
+
image: Optional[Image] = DEFAULT_IMAGE,
|
103
103
|
accumulate: Optional[Type[BaseModel]] = None,
|
104
|
-
|
105
|
-
max_batch_size: Optional[int] = None,
|
106
|
-
output_encoder: Optional[str] = "cloudpickle",
|
104
|
+
payload_encoder: Optional[str] = "cloudpickle",
|
107
105
|
placement_constraints: List[PlacementConstraints] = [],
|
108
106
|
):
|
109
107
|
def construct(fn):
|
@@ -128,9 +126,7 @@ def indexify_function(
|
|
128
126
|
|
129
127
|
IndexifyFn.image = image
|
130
128
|
IndexifyFn.accumulate = accumulate
|
131
|
-
IndexifyFn.
|
132
|
-
IndexifyFn.max_batch_size = max_batch_size
|
133
|
-
IndexifyFn.payload_encoder = output_encoder
|
129
|
+
IndexifyFn.payload_encoder = payload_encoder
|
134
130
|
return IndexifyFn
|
135
131
|
|
136
132
|
return construct
|
@@ -49,7 +49,7 @@ class LocalClient(IndexifyClient):
|
|
49
49
|
serializer = get_serializer(
|
50
50
|
g.get_function(k).indexify_function.payload_encoder
|
51
51
|
)
|
52
|
-
self._accumulators[k] = IndexifyData(payload=serializer.
|
52
|
+
self._accumulators[k] = IndexifyData(payload=serializer.serialize(v))
|
53
53
|
self._results[input.id] = outputs
|
54
54
|
self._run(g, input, outputs)
|
55
55
|
return input.id
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|