indexify 0.2.39__py3-none-any.whl → 0.2.41__py3-none-any.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.
Files changed (42) hide show
  1. indexify/cli.py +92 -52
  2. indexify/executor/agent.py +99 -187
  3. indexify/executor/api_objects.py +2 -8
  4. indexify/executor/downloader.py +129 -90
  5. indexify/executor/executor_tasks.py +15 -30
  6. indexify/executor/function_executor/function_executor.py +32 -0
  7. indexify/executor/function_executor/function_executor_factory.py +26 -0
  8. indexify/executor/function_executor/function_executor_map.py +91 -0
  9. indexify/executor/function_executor/process_function_executor.py +64 -0
  10. indexify/executor/function_executor/process_function_executor_factory.py +102 -0
  11. indexify/executor/function_worker.py +227 -184
  12. indexify/executor/runtime_probes.py +9 -8
  13. indexify/executor/task_fetcher.py +80 -0
  14. indexify/executor/task_reporter.py +18 -25
  15. indexify/executor/task_store.py +35 -16
  16. indexify/function_executor/function_executor_service.py +86 -0
  17. indexify/function_executor/handlers/run_function/function_inputs_loader.py +54 -0
  18. indexify/function_executor/handlers/run_function/handler.py +149 -0
  19. indexify/function_executor/handlers/run_function/request_validator.py +24 -0
  20. indexify/function_executor/handlers/run_function/response_helper.py +98 -0
  21. indexify/function_executor/initialize_request_validator.py +22 -0
  22. indexify/function_executor/proto/configuration.py +13 -0
  23. indexify/function_executor/proto/function_executor.proto +70 -0
  24. indexify/function_executor/proto/function_executor_pb2.py +53 -0
  25. indexify/function_executor/proto/function_executor_pb2.pyi +125 -0
  26. indexify/function_executor/proto/function_executor_pb2_grpc.py +163 -0
  27. indexify/function_executor/proto/message_validator.py +38 -0
  28. indexify/function_executor/server.py +31 -0
  29. indexify/functions_sdk/data_objects.py +0 -9
  30. indexify/functions_sdk/graph.py +17 -10
  31. indexify/functions_sdk/graph_definition.py +3 -2
  32. indexify/functions_sdk/image.py +35 -30
  33. indexify/functions_sdk/indexify_functions.py +5 -5
  34. indexify/http_client.py +15 -23
  35. indexify/logging.py +32 -0
  36. {indexify-0.2.39.dist-info → indexify-0.2.41.dist-info}/METADATA +3 -1
  37. indexify-0.2.41.dist-info/RECORD +53 -0
  38. indexify/executor/indexify_executor.py +0 -32
  39. indexify-0.2.39.dist-info/RECORD +0 -34
  40. {indexify-0.2.39.dist-info → indexify-0.2.41.dist-info}/LICENSE.txt +0 -0
  41. {indexify-0.2.39.dist-info → indexify-0.2.41.dist-info}/WHEEL +0 -0
  42. {indexify-0.2.39.dist-info → indexify-0.2.41.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,53 @@
1
+ # -*- coding: utf-8 -*-
2
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
3
+ # NO CHECKED-IN PROTOBUF GENCODE
4
+ # source: indexify/function_executor/proto/function_executor.proto
5
+ # Protobuf Python Version: 5.28.1
6
+ """Generated protocol buffer code."""
7
+ from google.protobuf import descriptor as _descriptor
8
+ from google.protobuf import descriptor_pool as _descriptor_pool
9
+ from google.protobuf import runtime_version as _runtime_version
10
+ from google.protobuf import symbol_database as _symbol_database
11
+ from google.protobuf.internal import builder as _builder
12
+
13
+ _runtime_version.ValidateProtobufRuntimeVersion(
14
+ _runtime_version.Domain.PUBLIC,
15
+ 5,
16
+ 28,
17
+ 1,
18
+ "",
19
+ "indexify/function_executor/proto/function_executor.proto",
20
+ )
21
+ # @@protoc_insertion_point(imports)
22
+
23
+ _sym_db = _symbol_database.Default()
24
+
25
+
26
+ DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(
27
+ b'\n8indexify/function_executor/proto/function_executor.proto\x12\x19\x66unction_executor_service"i\n\x10SerializedObject\x12\x0f\n\x05\x62ytes\x18\x01 \x01(\x0cH\x00\x12\x10\n\x06string\x18\x02 \x01(\tH\x00\x12\x19\n\x0c\x63ontent_type\x18\x03 \x01(\tH\x01\x88\x01\x01\x42\x06\n\x04\x64\x61taB\x0f\n\r_content_type"\x88\x02\n\x11InitializeRequest\x12\x16\n\tnamespace\x18\x01 \x01(\tH\x00\x88\x01\x01\x12\x17\n\ngraph_name\x18\x02 \x01(\tH\x01\x88\x01\x01\x12\x1a\n\rgraph_version\x18\x03 \x01(\x05H\x02\x88\x01\x01\x12\x1a\n\rfunction_name\x18\x05 \x01(\tH\x03\x88\x01\x01\x12?\n\x05graph\x18\x07 \x01(\x0b\x32+.function_executor_service.SerializedObjectH\x04\x88\x01\x01\x42\x0c\n\n_namespaceB\r\n\x0b_graph_nameB\x10\n\x0e_graph_versionB\x10\n\x0e_function_nameB\x08\n\x06_graph"6\n\x12InitializeResponse\x12\x14\n\x07success\x18\x01 \x01(\x08H\x00\x88\x01\x01\x42\n\n\x08_success"N\n\x0e\x46unctionOutput\x12<\n\x07outputs\x18\x01 \x03(\x0b\x32+.function_executor_service.SerializedObject"\x1d\n\x0cRouterOutput\x12\r\n\x05\x65\x64ges\x18\x01 \x03(\t"\xb0\x02\n\x0eRunTaskRequest\x12 \n\x13graph_invocation_id\x18\x04 \x01(\tH\x00\x88\x01\x01\x12\x14\n\x07task_id\x18\x06 \x01(\tH\x01\x88\x01\x01\x12H\n\x0e\x66unction_input\x18\t \x01(\x0b\x32+.function_executor_service.SerializedObjectH\x02\x88\x01\x01\x12M\n\x13\x66unction_init_value\x18\n \x01(\x0b\x32+.function_executor_service.SerializedObjectH\x03\x88\x01\x01\x42\x16\n\x14_graph_invocation_idB\n\n\x08_task_idB\x11\n\x0f_function_inputB\x16\n\x14_function_init_value"\xf1\x02\n\x0fRunTaskResponse\x12\x14\n\x07task_id\x18\x01 \x01(\tH\x00\x88\x01\x01\x12G\n\x0f\x66unction_output\x18\x02 \x01(\x0b\x32).function_executor_service.FunctionOutputH\x01\x88\x01\x01\x12\x43\n\rrouter_output\x18\x03 \x01(\x0b\x32\'.function_executor_service.RouterOutputH\x02\x88\x01\x01\x12\x13\n\x06stdout\x18\x04 \x01(\tH\x03\x88\x01\x01\x12\x13\n\x06stderr\x18\x05 \x01(\tH\x04\x88\x01\x01\x12\x17\n\nis_reducer\x18\x06 \x01(\x08H\x05\x88\x01\x01\x12\x14\n\x07success\x18\x07 \x01(\x08H\x06\x88\x01\x01\x42\n\n\x08_task_idB\x12\n\x10_function_outputB\x10\n\x0e_router_outputB\t\n\x07_stdoutB\t\n\x07_stderrB\r\n\x0b_is_reducerB\n\n\x08_success2\xe0\x01\n\x10\x46unctionExecutor\x12i\n\ninitialize\x12,.function_executor_service.InitializeRequest\x1a-.function_executor_service.InitializeResponse\x12\x61\n\x08run_task\x12).function_executor_service.RunTaskRequest\x1a*.function_executor_service.RunTaskResponseb\x06proto3'
28
+ )
29
+
30
+ _globals = globals()
31
+ _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
32
+ _builder.BuildTopDescriptorsAndMessages(
33
+ DESCRIPTOR, "indexify.function_executor.proto.function_executor_pb2", _globals
34
+ )
35
+ if not _descriptor._USE_C_DESCRIPTORS:
36
+ DESCRIPTOR._loaded_options = None
37
+ _globals["_SERIALIZEDOBJECT"]._serialized_start = 87
38
+ _globals["_SERIALIZEDOBJECT"]._serialized_end = 192
39
+ _globals["_INITIALIZEREQUEST"]._serialized_start = 195
40
+ _globals["_INITIALIZEREQUEST"]._serialized_end = 459
41
+ _globals["_INITIALIZERESPONSE"]._serialized_start = 461
42
+ _globals["_INITIALIZERESPONSE"]._serialized_end = 515
43
+ _globals["_FUNCTIONOUTPUT"]._serialized_start = 517
44
+ _globals["_FUNCTIONOUTPUT"]._serialized_end = 595
45
+ _globals["_ROUTEROUTPUT"]._serialized_start = 597
46
+ _globals["_ROUTEROUTPUT"]._serialized_end = 626
47
+ _globals["_RUNTASKREQUEST"]._serialized_start = 629
48
+ _globals["_RUNTASKREQUEST"]._serialized_end = 933
49
+ _globals["_RUNTASKRESPONSE"]._serialized_start = 936
50
+ _globals["_RUNTASKRESPONSE"]._serialized_end = 1305
51
+ _globals["_FUNCTIONEXECUTOR"]._serialized_start = 1308
52
+ _globals["_FUNCTIONEXECUTOR"]._serialized_end = 1532
53
+ # @@protoc_insertion_point(module_scope)
@@ -0,0 +1,125 @@
1
+ from typing import ClassVar as _ClassVar
2
+ from typing import Iterable as _Iterable
3
+ from typing import Mapping as _Mapping
4
+ from typing import Optional as _Optional
5
+ from typing import Union as _Union
6
+
7
+ from google.protobuf import descriptor as _descriptor
8
+ from google.protobuf import message as _message
9
+ from google.protobuf.internal import containers as _containers
10
+
11
+ DESCRIPTOR: _descriptor.FileDescriptor
12
+
13
+ class SerializedObject(_message.Message):
14
+ __slots__ = ("bytes", "string", "content_type")
15
+ BYTES_FIELD_NUMBER: _ClassVar[int]
16
+ STRING_FIELD_NUMBER: _ClassVar[int]
17
+ CONTENT_TYPE_FIELD_NUMBER: _ClassVar[int]
18
+ bytes: bytes
19
+ string: str
20
+ content_type: str
21
+ def __init__(
22
+ self,
23
+ bytes: _Optional[bytes] = ...,
24
+ string: _Optional[str] = ...,
25
+ content_type: _Optional[str] = ...,
26
+ ) -> None: ...
27
+
28
+ class InitializeRequest(_message.Message):
29
+ __slots__ = ("namespace", "graph_name", "graph_version", "function_name", "graph")
30
+ NAMESPACE_FIELD_NUMBER: _ClassVar[int]
31
+ GRAPH_NAME_FIELD_NUMBER: _ClassVar[int]
32
+ GRAPH_VERSION_FIELD_NUMBER: _ClassVar[int]
33
+ FUNCTION_NAME_FIELD_NUMBER: _ClassVar[int]
34
+ GRAPH_FIELD_NUMBER: _ClassVar[int]
35
+ namespace: str
36
+ graph_name: str
37
+ graph_version: int
38
+ function_name: str
39
+ graph: SerializedObject
40
+ def __init__(
41
+ self,
42
+ namespace: _Optional[str] = ...,
43
+ graph_name: _Optional[str] = ...,
44
+ graph_version: _Optional[int] = ...,
45
+ function_name: _Optional[str] = ...,
46
+ graph: _Optional[_Union[SerializedObject, _Mapping]] = ...,
47
+ ) -> None: ...
48
+
49
+ class InitializeResponse(_message.Message):
50
+ __slots__ = ("success",)
51
+ SUCCESS_FIELD_NUMBER: _ClassVar[int]
52
+ success: bool
53
+ def __init__(self, success: bool = ...) -> None: ...
54
+
55
+ class FunctionOutput(_message.Message):
56
+ __slots__ = ("outputs",)
57
+ OUTPUTS_FIELD_NUMBER: _ClassVar[int]
58
+ outputs: _containers.RepeatedCompositeFieldContainer[SerializedObject]
59
+ def __init__(
60
+ self, outputs: _Optional[_Iterable[_Union[SerializedObject, _Mapping]]] = ...
61
+ ) -> None: ...
62
+
63
+ class RouterOutput(_message.Message):
64
+ __slots__ = ("edges",)
65
+ EDGES_FIELD_NUMBER: _ClassVar[int]
66
+ edges: _containers.RepeatedScalarFieldContainer[str]
67
+ def __init__(self, edges: _Optional[_Iterable[str]] = ...) -> None: ...
68
+
69
+ class RunTaskRequest(_message.Message):
70
+ __slots__ = (
71
+ "graph_invocation_id",
72
+ "task_id",
73
+ "function_input",
74
+ "function_init_value",
75
+ )
76
+ GRAPH_INVOCATION_ID_FIELD_NUMBER: _ClassVar[int]
77
+ TASK_ID_FIELD_NUMBER: _ClassVar[int]
78
+ FUNCTION_INPUT_FIELD_NUMBER: _ClassVar[int]
79
+ FUNCTION_INIT_VALUE_FIELD_NUMBER: _ClassVar[int]
80
+ graph_invocation_id: str
81
+ task_id: str
82
+ function_input: SerializedObject
83
+ function_init_value: SerializedObject
84
+ def __init__(
85
+ self,
86
+ graph_invocation_id: _Optional[str] = ...,
87
+ task_id: _Optional[str] = ...,
88
+ function_input: _Optional[_Union[SerializedObject, _Mapping]] = ...,
89
+ function_init_value: _Optional[_Union[SerializedObject, _Mapping]] = ...,
90
+ ) -> None: ...
91
+
92
+ class RunTaskResponse(_message.Message):
93
+ __slots__ = (
94
+ "task_id",
95
+ "function_output",
96
+ "router_output",
97
+ "stdout",
98
+ "stderr",
99
+ "is_reducer",
100
+ "success",
101
+ )
102
+ TASK_ID_FIELD_NUMBER: _ClassVar[int]
103
+ FUNCTION_OUTPUT_FIELD_NUMBER: _ClassVar[int]
104
+ ROUTER_OUTPUT_FIELD_NUMBER: _ClassVar[int]
105
+ STDOUT_FIELD_NUMBER: _ClassVar[int]
106
+ STDERR_FIELD_NUMBER: _ClassVar[int]
107
+ IS_REDUCER_FIELD_NUMBER: _ClassVar[int]
108
+ SUCCESS_FIELD_NUMBER: _ClassVar[int]
109
+ task_id: str
110
+ function_output: FunctionOutput
111
+ router_output: RouterOutput
112
+ stdout: str
113
+ stderr: str
114
+ is_reducer: bool
115
+ success: bool
116
+ def __init__(
117
+ self,
118
+ task_id: _Optional[str] = ...,
119
+ function_output: _Optional[_Union[FunctionOutput, _Mapping]] = ...,
120
+ router_output: _Optional[_Union[RouterOutput, _Mapping]] = ...,
121
+ stdout: _Optional[str] = ...,
122
+ stderr: _Optional[str] = ...,
123
+ is_reducer: bool = ...,
124
+ success: bool = ...,
125
+ ) -> None: ...
@@ -0,0 +1,163 @@
1
+ # Generated by the gRPC Python protocol compiler plugin. DO NOT EDIT!
2
+ """Client and server classes corresponding to protobuf-defined services."""
3
+ import warnings
4
+
5
+ import grpc
6
+
7
+ from indexify.function_executor.proto import (
8
+ function_executor_pb2 as indexify_dot_function__executor_dot_proto_dot_function__executor__pb2,
9
+ )
10
+
11
+ GRPC_GENERATED_VERSION = "1.68.1"
12
+ GRPC_VERSION = grpc.__version__
13
+ _version_not_supported = False
14
+
15
+ try:
16
+ from grpc._utilities import first_version_is_lower
17
+
18
+ _version_not_supported = first_version_is_lower(
19
+ GRPC_VERSION, GRPC_GENERATED_VERSION
20
+ )
21
+ except ImportError:
22
+ _version_not_supported = True
23
+
24
+ if _version_not_supported:
25
+ raise RuntimeError(
26
+ f"The grpc package installed is at version {GRPC_VERSION},"
27
+ + f" but the generated code in indexify/function_executor/proto/function_executor_pb2_grpc.py depends on"
28
+ + f" grpcio>={GRPC_GENERATED_VERSION}."
29
+ + f" Please upgrade your grpc module to grpcio>={GRPC_GENERATED_VERSION}"
30
+ + f" or downgrade your generated code using grpcio-tools<={GRPC_VERSION}."
31
+ )
32
+
33
+
34
+ class FunctionExecutorStub(object):
35
+ """Missing associated documentation comment in .proto file."""
36
+
37
+ def __init__(self, channel):
38
+ """Constructor.
39
+
40
+ Args:
41
+ channel: A grpc.Channel.
42
+ """
43
+ self.initialize = channel.unary_unary(
44
+ "/function_executor_service.FunctionExecutor/initialize",
45
+ request_serializer=indexify_dot_function__executor_dot_proto_dot_function__executor__pb2.InitializeRequest.SerializeToString,
46
+ response_deserializer=indexify_dot_function__executor_dot_proto_dot_function__executor__pb2.InitializeResponse.FromString,
47
+ _registered_method=True,
48
+ )
49
+ self.run_task = channel.unary_unary(
50
+ "/function_executor_service.FunctionExecutor/run_task",
51
+ request_serializer=indexify_dot_function__executor_dot_proto_dot_function__executor__pb2.RunTaskRequest.SerializeToString,
52
+ response_deserializer=indexify_dot_function__executor_dot_proto_dot_function__executor__pb2.RunTaskResponse.FromString,
53
+ _registered_method=True,
54
+ )
55
+
56
+
57
+ class FunctionExecutorServicer(object):
58
+ """Missing associated documentation comment in .proto file."""
59
+
60
+ def initialize(self, request, context):
61
+ """Initializes the Function Executor to run tasks
62
+ for a particular function. This method is called only
63
+ once per Function Executor as it can only run a single function.
64
+ It should be called before calling RunTask for the function.
65
+ """
66
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
67
+ context.set_details("Method not implemented!")
68
+ raise NotImplementedError("Method not implemented!")
69
+
70
+ def run_task(self, request, context):
71
+ """Executes the task defined in the request.
72
+ Multiple tasks can be running in parallel.
73
+ """
74
+ context.set_code(grpc.StatusCode.UNIMPLEMENTED)
75
+ context.set_details("Method not implemented!")
76
+ raise NotImplementedError("Method not implemented!")
77
+
78
+
79
+ def add_FunctionExecutorServicer_to_server(servicer, server):
80
+ rpc_method_handlers = {
81
+ "initialize": grpc.unary_unary_rpc_method_handler(
82
+ servicer.initialize,
83
+ request_deserializer=indexify_dot_function__executor_dot_proto_dot_function__executor__pb2.InitializeRequest.FromString,
84
+ response_serializer=indexify_dot_function__executor_dot_proto_dot_function__executor__pb2.InitializeResponse.SerializeToString,
85
+ ),
86
+ "run_task": grpc.unary_unary_rpc_method_handler(
87
+ servicer.run_task,
88
+ request_deserializer=indexify_dot_function__executor_dot_proto_dot_function__executor__pb2.RunTaskRequest.FromString,
89
+ response_serializer=indexify_dot_function__executor_dot_proto_dot_function__executor__pb2.RunTaskResponse.SerializeToString,
90
+ ),
91
+ }
92
+ generic_handler = grpc.method_handlers_generic_handler(
93
+ "function_executor_service.FunctionExecutor", rpc_method_handlers
94
+ )
95
+ server.add_generic_rpc_handlers((generic_handler,))
96
+ server.add_registered_method_handlers(
97
+ "function_executor_service.FunctionExecutor", rpc_method_handlers
98
+ )
99
+
100
+
101
+ # This class is part of an EXPERIMENTAL API.
102
+ class FunctionExecutor(object):
103
+ """Missing associated documentation comment in .proto file."""
104
+
105
+ @staticmethod
106
+ def initialize(
107
+ request,
108
+ target,
109
+ options=(),
110
+ channel_credentials=None,
111
+ call_credentials=None,
112
+ insecure=False,
113
+ compression=None,
114
+ wait_for_ready=None,
115
+ timeout=None,
116
+ metadata=None,
117
+ ):
118
+ return grpc.experimental.unary_unary(
119
+ request,
120
+ target,
121
+ "/function_executor_service.FunctionExecutor/initialize",
122
+ indexify_dot_function__executor_dot_proto_dot_function__executor__pb2.InitializeRequest.SerializeToString,
123
+ indexify_dot_function__executor_dot_proto_dot_function__executor__pb2.InitializeResponse.FromString,
124
+ options,
125
+ channel_credentials,
126
+ insecure,
127
+ call_credentials,
128
+ compression,
129
+ wait_for_ready,
130
+ timeout,
131
+ metadata,
132
+ _registered_method=True,
133
+ )
134
+
135
+ @staticmethod
136
+ def run_task(
137
+ request,
138
+ target,
139
+ options=(),
140
+ channel_credentials=None,
141
+ call_credentials=None,
142
+ insecure=False,
143
+ compression=None,
144
+ wait_for_ready=None,
145
+ timeout=None,
146
+ metadata=None,
147
+ ):
148
+ return grpc.experimental.unary_unary(
149
+ request,
150
+ target,
151
+ "/function_executor_service.FunctionExecutor/run_task",
152
+ indexify_dot_function__executor_dot_proto_dot_function__executor__pb2.RunTaskRequest.SerializeToString,
153
+ indexify_dot_function__executor_dot_proto_dot_function__executor__pb2.RunTaskResponse.FromString,
154
+ options,
155
+ channel_credentials,
156
+ insecure,
157
+ call_credentials,
158
+ compression,
159
+ wait_for_ready,
160
+ timeout,
161
+ metadata,
162
+ _registered_method=True,
163
+ )
@@ -0,0 +1,38 @@
1
+ from typing import Any, Self
2
+
3
+ from .function_executor_pb2 import SerializedObject
4
+
5
+
6
+ class MessageValidator:
7
+ def __init__(self, message: Any):
8
+ self._message = message
9
+
10
+ def required_field(self, field_name: str) -> Self:
11
+ if not self._message.HasField(field_name):
12
+ raise ValueError(
13
+ f"Field '{field_name}' is required in {type(self._message).__name__}"
14
+ )
15
+ return self
16
+
17
+ def required_serialized_object(self, field_name: str) -> Self:
18
+ """Validates the SerializedObject.
19
+
20
+ Raises: ValueError: If the SerializedObject is invalid or not present."""
21
+ self.required_field(field_name)
22
+ return self.optional_serialized_object(field_name)
23
+
24
+ def optional_serialized_object(self, field_name: str) -> Self:
25
+ """Validates the SerializedObject.
26
+
27
+ Raises: ValueError: If the SerializedObject is invalid."""
28
+ if not self._message.HasField(field_name):
29
+ return self
30
+
31
+ serializedObject: SerializedObject = getattr(self._message, field_name)
32
+ if not serializedObject.HasField("string") and not serializedObject.HasField(
33
+ "bytes"
34
+ ):
35
+ raise ValueError("oneof 'data' is requred in SerializedObject")
36
+ if not serializedObject.HasField("content_type"):
37
+ raise ValueError("Field 'content_type' is requred in SerializedObject")
38
+ return self
@@ -0,0 +1,31 @@
1
+ from concurrent.futures import ThreadPoolExecutor
2
+
3
+ import grpc
4
+
5
+ from .function_executor_service import FunctionExecutorService
6
+ from .proto.configuration import GRPC_SERVER_OPTIONS
7
+ from .proto.function_executor_pb2_grpc import (
8
+ add_FunctionExecutorServicer_to_server,
9
+ )
10
+
11
+ # Temporary limit until we have a better way to control this.
12
+ # This limits the number of concurrent tasks that Function Executor can run.
13
+ MAX_RPC_CONCURRENCY = 100
14
+
15
+
16
+ class Server:
17
+ def __init__(self, server_address: str, service: FunctionExecutorService):
18
+ self._server_address: str = server_address
19
+ self._service: FunctionExecutorService = service
20
+
21
+ def run(self):
22
+ """Runs Function Executor Service at the configured address."""
23
+ server = grpc.server(
24
+ thread_pool=ThreadPoolExecutor(max_workers=MAX_RPC_CONCURRENCY),
25
+ maximum_concurrent_rpcs=MAX_RPC_CONCURRENCY,
26
+ options=GRPC_SERVER_OPTIONS,
27
+ )
28
+ add_FunctionExecutorServicer_to_server(self._service, server)
29
+ server.add_insecure_port(self._server_address)
30
+ server.start()
31
+ server.wait_for_termination()
@@ -20,15 +20,6 @@ class IndexifyData(BaseModel):
20
20
  encoder: Literal["cloudpickle", "json"] = "cloudpickle"
21
21
 
22
22
 
23
- class FunctionWorkerOutput(BaseModel):
24
- fn_outputs: Optional[List[IndexifyData]]
25
- router_output: Optional[RouterOutput]
26
- stdout: Optional[str]
27
- stderr: Optional[str]
28
- reducer: bool = False
29
- success: bool = True
30
-
31
-
32
23
  class File(BaseModel):
33
24
  data: bytes
34
25
  mime_type: Optional[str] = None
@@ -1,4 +1,4 @@
1
- import json
1
+ import importlib
2
2
  import sys
3
3
  from collections import defaultdict
4
4
  from queue import deque
@@ -65,7 +65,11 @@ def is_pydantic_model_from_annotation(type_annotation):
65
65
 
66
66
  class Graph:
67
67
  def __init__(
68
- self, name: str, start_node: IndexifyFunction, description: Optional[str] = None
68
+ self,
69
+ name: str,
70
+ start_node: IndexifyFunction,
71
+ description: Optional[str] = None,
72
+ tags: Dict[str, str] = {},
69
73
  ):
70
74
  self.name = name
71
75
  self.description = description
@@ -73,6 +77,7 @@ class Graph:
73
77
  self.routers: Dict[str, List[str]] = defaultdict(list)
74
78
  self.edges: Dict[str, List[str]] = defaultdict(list)
75
79
  self.accumulator_zero_values: Dict[str, Any] = {}
80
+ self.tags = tags
76
81
 
77
82
  self.add_node(start_node)
78
83
  if issubclass(start_node, IndexifyRouter):
@@ -169,7 +174,6 @@ class Graph:
169
174
  fn_name=start_node.name,
170
175
  description=start_node.description,
171
176
  reducer=is_reducer,
172
- image_name=start_node.image._image_name,
173
177
  image_information=start_node.image.to_image_information(),
174
178
  input_encoder=start_node.input_encoder,
175
179
  output_encoder=start_node.output_encoder,
@@ -186,7 +190,6 @@ class Graph:
186
190
  target_fns=self.routers[node_name],
187
191
  input_encoder=node.input_encoder,
188
192
  output_encoder=node.output_encoder,
189
- image_name=node.image._image_name,
190
193
  image_information=node.image.to_image_information(),
191
194
  )
192
195
  )
@@ -197,7 +200,6 @@ class Graph:
197
200
  fn_name=node.name,
198
201
  description=node.description,
199
202
  reducer=node.accumulate is not None,
200
- image_name=node.image._image_name,
201
203
  image_information=node.image.to_image_information(),
202
204
  input_encoder=node.input_encoder,
203
205
  output_encoder=node.output_encoder,
@@ -210,9 +212,11 @@ class Graph:
210
212
  start_node=NodeMetadata(compute_fn=start_node),
211
213
  nodes=metadata_nodes,
212
214
  edges=metadata_edges,
215
+ tags=self.tags,
213
216
  runtime_information=RuntimeInformation(
214
217
  major_version=sys.version_info.major,
215
218
  minor_version=sys.version_info.minor,
219
+ sdk_version=importlib.metadata.version("indexify"),
216
220
  ),
217
221
  )
218
222
 
@@ -257,9 +261,14 @@ class Graph:
257
261
  current_node_name = queue.popleft()
258
262
  neighbours = (
259
263
  self.edges[current_node_name]
260
- if self.edges[current_node_name]
261
- else self.routers[current_node_name]
264
+ if current_node_name in self.edges
265
+ else (
266
+ self.routers[current_node_name]
267
+ if current_node_name in self.routers
268
+ else []
269
+ )
262
270
  )
271
+
263
272
  for neighbour in neighbours:
264
273
  if neighbour in visited:
265
274
  continue
@@ -269,9 +278,7 @@ class Graph:
269
278
 
270
279
  if total_number_of_nodes != len(visited):
271
280
  # all the nodes are not reachable from the start_node.
272
- raise Exception(
273
- "Some nodes in the graph are not reachable from start node."
274
- )
281
+ raise Exception("Some nodes in the graph are not reachable from start node")
275
282
 
276
283
  def _run(
277
284
  self,
@@ -12,7 +12,6 @@ class FunctionMetadata(BaseModel):
12
12
  fn_name: str
13
13
  description: str
14
14
  reducer: bool = False
15
- image_name: str
16
15
  image_information: ImageInformation
17
16
  input_encoder: str = "cloudpickle"
18
17
  output_encoder: str = "cloudpickle"
@@ -23,7 +22,6 @@ class RouterMetadata(BaseModel):
23
22
  description: str
24
23
  source_fn: str
25
24
  target_fns: List[str]
26
- image_name: str
27
25
  image_information: ImageInformation
28
26
  input_encoder: str = "cloudpickle"
29
27
  output_encoder: str = "cloudpickle"
@@ -38,17 +36,20 @@ class NodeMetadata(BaseModel):
38
36
  class RuntimeInformation(BaseModel):
39
37
  major_version: int
40
38
  minor_version: int
39
+ sdk_version: str
41
40
 
42
41
 
43
42
  class ComputeGraphMetadata(BaseModel):
44
43
  name: str
45
44
  description: str
46
45
  start_node: NodeMetadata
46
+ tags: Dict[str, str] = {}
47
47
  nodes: Dict[str, NodeMetadata]
48
48
  edges: Dict[str, List[str]]
49
49
  accumulator_zero_values: Dict[str, bytes] = {}
50
50
  runtime_information: RuntimeInformation
51
51
  replaying: bool = False
52
+ version: Optional[int] = -1
52
53
 
53
54
  def get_input_payload_serializer(self):
54
55
  return get_serializer(self.start_node.compute_fn.input_encoder)
@@ -1,34 +1,29 @@
1
- from typing import List
1
+ import hashlib
2
+ import importlib
3
+ import sys
4
+ from typing import List, Optional
2
5
 
3
6
  from pydantic import BaseModel
4
7
 
5
8
 
6
- def python_version_to_image(python_version):
7
- if python_version.startswith("3.9"):
8
- return "python:3.9.20-bookworm"
9
- elif python_version.startswith("3.10"):
10
- return "python:3.10.15-bookworm"
11
- elif python_version.startswith("3.11"):
12
- return "python:3.11.10-bookworm"
13
- else:
14
- raise ValueError(f"unsupported Python version: {python_version}")
15
-
16
-
17
9
  # Pydantic object for API
18
10
  class ImageInformation(BaseModel):
19
11
  image_name: str
20
12
  tag: str
21
13
  base_image: str
22
14
  run_strs: List[str]
15
+ image_url: Optional[str] = ""
16
+ sdk_version: str
23
17
 
24
18
 
25
19
  class Image:
26
- def __init__(self, python="3.10"):
20
+ def __init__(self):
27
21
  self._image_name = None
28
22
  self._tag = "latest"
29
- self._base_image = python_version_to_image(python)
30
- self._python_version = python
23
+ self._base_image = BASE_IMAGE_NAME
24
+ self._python_version = LOCAL_PYTHON_VERSION
31
25
  self._run_strs = []
26
+ self._sdk_version = importlib.metadata.version("indexify")
32
27
 
33
28
  def name(self, image_name):
34
29
  self._image_name = image_name
@@ -52,21 +47,31 @@ class Image:
52
47
  tag=self._tag,
53
48
  base_image=self._base_image,
54
49
  run_strs=self._run_strs,
50
+ sdk_version=self._sdk_version,
55
51
  )
56
52
 
53
+ def hash(self) -> str:
54
+ hash = hashlib.sha256(
55
+ self._image_name.encode()
56
+ ) # Make a hash of the image name
57
+ hash.update(self._base_image.encode())
58
+ hash.update("".join(self._run_strs).encode())
59
+ hash.update(self._sdk_version.encode())
60
+
61
+ return hash.hexdigest()
62
+
63
+
64
+ LOCAL_PYTHON_VERSION = f"{sys.version_info.major}.{sys.version_info.minor}"
65
+ BASE_IMAGE_NAME = f"python:{LOCAL_PYTHON_VERSION}-slim-bookworm"
66
+
67
+
68
+ def GetDefaultPythonImage(python_version: str):
69
+ return (
70
+ Image()
71
+ .name("tensorlake/indexify-executor-default")
72
+ .base_image(f"python:{python_version}-slim-bookworm")
73
+ .tag(python_version)
74
+ )
75
+
57
76
 
58
- DEFAULT_IMAGE_3_10 = (
59
- Image()
60
- .name("tensorlake/indexify-executor-default")
61
- .base_image("python:3.10.15-slim-bookworm")
62
- .tag("3.10")
63
- .run("pip install indexify")
64
- )
65
-
66
- DEFAULT_IMAGE_3_11 = (
67
- Image()
68
- .name("tensorlake/indexify-executor-default")
69
- .base_image("python:3.11.10-slim-bookworm")
70
- .tag("3.11")
71
- .run("pip install indexify")
72
- )
77
+ DEFAULT_IMAGE = GetDefaultPythonImage(LOCAL_PYTHON_VERSION)