indexify 0.3.4__py3-none-any.whl → 0.3.6__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 (40) hide show
  1. indexify/cli/cli.py +6 -4
  2. indexify/executor/downloader.py +2 -3
  3. indexify/executor/executor.py +8 -7
  4. indexify/executor/function_executor/function_executor.py +3 -4
  5. indexify/executor/function_executor/health_checker.py +4 -4
  6. indexify/executor/function_executor/invocation_state_client.py +3 -4
  7. indexify/{function_executor/proto/configuration.py → executor/function_executor/server/client_configuration.py} +7 -11
  8. indexify/executor/function_executor/server/subprocess_function_executor_server.py +1 -2
  9. indexify/executor/function_executor/server/subprocess_function_executor_server_factory.py +0 -5
  10. indexify/executor/function_executor/single_task_runner.py +2 -4
  11. indexify/executor/function_executor/task_input.py +1 -1
  12. indexify/executor/function_executor/task_output.py +1 -1
  13. indexify/executor/task_fetcher.py +5 -5
  14. indexify/executor/task_reporter.py +3 -4
  15. {indexify-0.3.4.dist-info → indexify-0.3.6.dist-info}/METADATA +3 -4
  16. indexify-0.3.6.dist-info/RECORD +25 -0
  17. indexify-0.3.6.dist-info/entry_points.txt +3 -0
  18. indexify/function_executor/README.md +0 -18
  19. indexify/function_executor/handlers/run_function/function_inputs_loader.py +0 -53
  20. indexify/function_executor/handlers/run_function/handler.py +0 -126
  21. indexify/function_executor/handlers/run_function/request_validator.py +0 -26
  22. indexify/function_executor/handlers/run_function/response_helper.py +0 -96
  23. indexify/function_executor/initialize_request_validator.py +0 -21
  24. indexify/function_executor/invocation_state/invocation_state_proxy_server.py +0 -170
  25. indexify/function_executor/invocation_state/proxied_invocation_state.py +0 -22
  26. indexify/function_executor/invocation_state/response_validator.py +0 -29
  27. indexify/function_executor/main.py +0 -50
  28. indexify/function_executor/proto/function_executor.proto +0 -130
  29. indexify/function_executor/proto/function_executor_pb2.py +0 -69
  30. indexify/function_executor/proto/function_executor_pb2.pyi +0 -225
  31. indexify/function_executor/proto/function_executor_pb2_grpc.py +0 -260
  32. indexify/function_executor/proto/message_validator.py +0 -38
  33. indexify/function_executor/server.py +0 -29
  34. indexify/function_executor/service.py +0 -133
  35. indexify/utils/README.md +0 -3
  36. indexify/utils/http_client.py +0 -88
  37. indexify/utils/logging.py +0 -66
  38. indexify-0.3.4.dist-info/RECORD +0 -45
  39. indexify-0.3.4.dist-info/entry_points.txt +0 -4
  40. {indexify-0.3.4.dist-info → indexify-0.3.6.dist-info}/WHEEL +0 -0
indexify/cli/cli.py CHANGED
@@ -1,4 +1,4 @@
1
- from indexify.utils.logging import (
1
+ from tensorlake.utils.logging import (
2
2
  configure_development_mode_logging,
3
3
  configure_logging_early,
4
4
  configure_production_mode_logging,
@@ -219,12 +219,13 @@ def executor(
219
219
  )
220
220
 
221
221
  id = nanoid.generate()
222
+ executor_version = version("indexify")
222
223
  logger.info(
223
224
  "starting executor",
224
225
  server_addr=server_addr,
225
226
  config_path=config_path,
226
227
  executor_id=id,
227
- executor_version=version("indexify"),
228
+ executor_version=executor_version,
228
229
  executor_cache=executor_cache,
229
230
  ports=ports,
230
231
  functions=function_uris,
@@ -247,7 +248,8 @@ def executor(
247
248
  exit(1)
248
249
 
249
250
  Executor(
250
- id,
251
+ id=id,
252
+ version=executor_version,
251
253
  server_addr=server_addr,
252
254
  config_path=config_path,
253
255
  code_path=executor_cache,
@@ -290,7 +292,7 @@ def _parse_function_uris(uri_strs: Optional[List[str]]) -> Optional[List[Functio
290
292
 
291
293
  def _create_image(image: Image, python_sdk_path):
292
294
  console.print(
293
- Text("Creating container for ", style="cyan"),
295
+ Text("Creating image for ", style="cyan"),
294
296
  Text(f"`{image._image_name}`", style="cyan bold"),
295
297
  )
296
298
  _build_image(image=image, python_sdk_path=python_sdk_path)
@@ -4,9 +4,8 @@ from typing import Any, Optional
4
4
 
5
5
  import httpx
6
6
  import structlog
7
-
8
- from indexify.function_executor.proto.function_executor_pb2 import SerializedObject
9
- from indexify.utils.http_client import get_httpx_client
7
+ from tensorlake.function_executor.proto.function_executor_pb2 import SerializedObject
8
+ from tensorlake.utils.http_client import get_httpx_client
10
9
 
11
10
  from .api_objects import Task
12
11
 
@@ -4,9 +4,8 @@ from pathlib import Path
4
4
  from typing import Any, List, Optional
5
5
 
6
6
  import structlog
7
-
8
- from indexify.function_executor.proto.function_executor_pb2 import SerializedObject
9
- from indexify.utils.logging import suppress as suppress_logging
7
+ from tensorlake.function_executor.proto.function_executor_pb2 import SerializedObject
8
+ from tensorlake.utils.logging import suppress as suppress_logging
10
9
 
11
10
  from .api_objects import FunctionURI, Task
12
11
  from .downloader import Downloader
@@ -21,7 +20,8 @@ from .task_runner import TaskInput, TaskOutput, TaskRunner
21
20
  class Executor:
22
21
  def __init__(
23
22
  self,
24
- executor_id: str,
23
+ id: str,
24
+ version: str,
25
25
  code_path: Path,
26
26
  function_allowlist: Optional[List[FunctionURI]],
27
27
  function_executor_server_factory: FunctionExecutorServerFactory,
@@ -48,15 +48,16 @@ class Executor:
48
48
  code_path=code_path, base_url=self._base_url, config_path=config_path
49
49
  )
50
50
  self._task_fetcher = TaskFetcher(
51
+ executor_id=id,
52
+ executor_version=version,
53
+ function_allowlist=function_allowlist,
51
54
  protocol=protocol,
52
55
  indexify_server_addr=self._server_addr,
53
- executor_id=executor_id,
54
- function_allowlist=function_allowlist,
55
56
  config_path=config_path,
56
57
  )
57
58
  self._task_reporter = TaskReporter(
58
59
  base_url=self._base_url,
59
- executor_id=executor_id,
60
+ executor_id=id,
60
61
  config_path=self._config_path,
61
62
  )
62
63
 
@@ -2,15 +2,14 @@ import asyncio
2
2
  from typing import Any, Optional
3
3
 
4
4
  import grpc
5
-
6
- from indexify.function_executor.proto.function_executor_pb2 import (
5
+ from tensorlake.function_executor.proto.function_executor_pb2 import (
7
6
  InitializeRequest,
8
7
  InitializeResponse,
9
8
  )
10
- from indexify.function_executor.proto.function_executor_pb2_grpc import (
9
+ from tensorlake.function_executor.proto.function_executor_pb2_grpc import (
11
10
  FunctionExecutorStub,
12
11
  )
13
- from indexify.utils.http_client import get_httpx_client
12
+ from tensorlake.utils.http_client import get_httpx_client
14
13
 
15
14
  from .health_checker import HealthChecker
16
15
  from .invocation_state_client import InvocationStateClient
@@ -3,16 +3,16 @@ from collections.abc import Awaitable, Callable
3
3
  from typing import Any, Optional
4
4
 
5
5
  from grpc.aio import AioRpcError
6
-
7
- from indexify.function_executor.proto.configuration import HEALTH_CHECK_TIMEOUT_SEC
8
- from indexify.function_executor.proto.function_executor_pb2 import (
6
+ from tensorlake.function_executor.proto.function_executor_pb2 import (
9
7
  HealthCheckRequest,
10
8
  HealthCheckResponse,
11
9
  )
12
- from indexify.function_executor.proto.function_executor_pb2_grpc import (
10
+ from tensorlake.function_executor.proto.function_executor_pb2_grpc import (
13
11
  FunctionExecutorStub,
14
12
  )
15
13
 
14
+ from .server.client_configuration import HEALTH_CHECK_TIMEOUT_SEC
15
+
16
16
  HEALTH_CHECK_POLL_PERIOD_SEC = 10
17
17
 
18
18
 
@@ -3,18 +3,17 @@ from typing import Any, AsyncGenerator, Optional, Union
3
3
 
4
4
  import grpc
5
5
  import httpx
6
-
7
- from indexify.function_executor.proto.function_executor_pb2 import (
6
+ from tensorlake.function_executor.proto.function_executor_pb2 import (
8
7
  GetInvocationStateResponse,
9
8
  InvocationStateRequest,
10
9
  InvocationStateResponse,
11
10
  SerializedObject,
12
11
  SetInvocationStateResponse,
13
12
  )
14
- from indexify.function_executor.proto.function_executor_pb2_grpc import (
13
+ from tensorlake.function_executor.proto.function_executor_pb2_grpc import (
15
14
  FunctionExecutorStub,
16
15
  )
17
- from indexify.function_executor.proto.message_validator import MessageValidator
16
+ from tensorlake.function_executor.proto.message_validator import MessageValidator
18
17
 
19
18
  from ..downloader import serialized_object_from_http_response
20
19
 
@@ -4,21 +4,17 @@
4
4
  # This is due to internal hard gRPC limits. When we want to increase the message sizes
5
5
  # we'll have to implement chunking for large messages.
6
6
  _MAX_GRPC_MESSAGE_LENGTH = -1
7
- # Disable port reuse: fail if multiple Function Executor Servers attempt to bind to the
8
- # same port. This happens when Indexify users misconfigure the Servers. Disabling the port
9
- # reuse results in a clear error message on Server startup instead of obscure errors later
10
- # while Indexify cluster is serving tasks.
11
- # If we don't disable port reuse then a random Server gets the requests so wrong tasks get
12
- # routed to wrong servers.
13
- _REUSE_SERVER_PORT = 0
14
7
 
15
- GRPC_SERVER_OPTIONS = [
8
+ # Optimize the channels for low latency connection establishement as we are running on the same host.
9
+ _RECONNECT_BACKOFF_MS = 100
10
+
11
+ GRPC_CHANNEL_OPTIONS = [
16
12
  ("grpc.max_receive_message_length", _MAX_GRPC_MESSAGE_LENGTH),
17
13
  ("grpc.max_send_message_length", _MAX_GRPC_MESSAGE_LENGTH),
18
- ("grpc.so_reuseport", _REUSE_SERVER_PORT),
14
+ ("grpc.min_reconnect_backoff_ms", _RECONNECT_BACKOFF_MS),
15
+ ("grpc.max_reconnect_backoff_ms", _RECONNECT_BACKOFF_MS),
16
+ ("grpc.initial_reconnect_backoff_ms", _RECONNECT_BACKOFF_MS),
19
17
  ]
20
18
 
21
- GRPC_CHANNEL_OPTIONS = GRPC_SERVER_OPTIONS
22
-
23
19
  # If a health check takes more than this duration then the server is considered unhealthy.
24
20
  HEALTH_CHECK_TIMEOUT_SEC = 5
@@ -3,8 +3,7 @@ from typing import Any
3
3
 
4
4
  import grpc
5
5
 
6
- from indexify.function_executor.proto.configuration import GRPC_CHANNEL_OPTIONS
7
-
6
+ from .client_configuration import GRPC_CHANNEL_OPTIONS
8
7
  from .function_executor_server import FunctionExecutorServer
9
8
 
10
9
 
@@ -22,11 +22,6 @@ class SubprocessFunctionExecutorServerFactory(FunctionExecutorServerFactory):
22
22
  async def create(
23
23
  self, config: FunctionExecutorServerConfiguration, logger: Any
24
24
  ) -> SubprocessFunctionExecutorServer:
25
- if config.image_uri is not None:
26
- raise ValueError(
27
- "SubprocessFunctionExecutorServerFactory doesn't support container images"
28
- )
29
-
30
25
  logger = logger.bind(module=__name__)
31
26
  port: Optional[int] = None
32
27
 
@@ -2,14 +2,12 @@ from collections.abc import Awaitable, Callable
2
2
  from typing import Any, Optional
3
3
 
4
4
  import grpc
5
- from grpc.aio import AioRpcError
6
-
7
- from indexify.function_executor.proto.function_executor_pb2 import (
5
+ from tensorlake.function_executor.proto.function_executor_pb2 import (
8
6
  InitializeRequest,
9
7
  RunTaskRequest,
10
8
  RunTaskResponse,
11
9
  )
12
- from indexify.function_executor.proto.function_executor_pb2_grpc import (
10
+ from tensorlake.function_executor.proto.function_executor_pb2_grpc import (
13
11
  FunctionExecutorStub,
14
12
  )
15
13
 
@@ -1,6 +1,6 @@
1
1
  from typing import Optional
2
2
 
3
- from indexify.function_executor.proto.function_executor_pb2 import SerializedObject
3
+ from tensorlake.function_executor.proto.function_executor_pb2 import SerializedObject
4
4
 
5
5
  from ..api_objects import Task
6
6
 
@@ -1,6 +1,6 @@
1
1
  from typing import Optional
2
2
 
3
- from indexify.function_executor.proto.function_executor_pb2 import (
3
+ from tensorlake.function_executor.proto.function_executor_pb2 import (
4
4
  FunctionOutput,
5
5
  RouterOutput,
6
6
  )
@@ -4,8 +4,7 @@ from typing import AsyncGenerator, List, Optional
4
4
 
5
5
  import structlog
6
6
  from httpx_sse import aconnect_sse
7
-
8
- from indexify.utils.http_client import get_httpx_client
7
+ from tensorlake.utils.http_client import get_httpx_client
9
8
 
10
9
  from .api_objects import ExecutorMetadata, FunctionURI, Task
11
10
  from .runtime_probes import ProbeInfo, RuntimeProbes
@@ -16,10 +15,11 @@ class TaskFetcher:
16
15
 
17
16
  def __init__(
18
17
  self,
19
- protocol: str,
20
- indexify_server_addr: str,
21
18
  executor_id: str,
19
+ executor_version: str,
22
20
  function_allowlist: Optional[List[FunctionURI]],
21
+ protocol: str,
22
+ indexify_server_addr: str,
23
23
  config_path: Optional[str] = None,
24
24
  ):
25
25
  self._protocol: str = protocol
@@ -30,7 +30,7 @@ class TaskFetcher:
30
30
  probe_info: ProbeInfo = RuntimeProbes().probe()
31
31
  self._executor_metadata: ExecutorMetadata = ExecutorMetadata(
32
32
  id=executor_id,
33
- executor_version=version("indexify"),
33
+ executor_version=executor_version,
34
34
  addr="",
35
35
  function_allowlist=function_allowlist,
36
36
  labels=probe_info.labels,
@@ -3,11 +3,10 @@ from typing import Any, List, Optional, Tuple
3
3
 
4
4
  import nanoid
5
5
  from httpx import Timeout
6
+ from tensorlake.function_executor.proto.function_executor_pb2 import FunctionOutput
7
+ from tensorlake.utils.http_client import get_httpx_client
6
8
 
7
- from indexify.function_executor.proto.function_executor_pb2 import FunctionOutput
8
- from indexify.utils.http_client import get_httpx_client
9
-
10
- from .api_objects import RouterOutput, Task, TaskResult
9
+ from .api_objects import RouterOutput, TaskResult
11
10
  from .task_runner import TaskOutput
12
11
 
13
12
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: indexify
3
- Version: 0.3.4
3
+ Version: 0.3.6
4
4
  Summary: Open Source Indexify components and helper tools
5
5
  Home-page: https://github.com/tensorlakeai/indexify
6
6
  License: Apache 2.0
@@ -14,8 +14,7 @@ Classifier: Programming Language :: Python :: 3.10
14
14
  Classifier: Programming Language :: Python :: 3.11
15
15
  Classifier: Programming Language :: Python :: 3.12
16
16
  Classifier: Programming Language :: Python :: 3.13
17
- Requires-Dist: grpcio (==1.68.1)
18
- Requires-Dist: grpcio-tools (==1.68.1)
17
+ Requires-Dist: grpcio (==1.70.0)
19
18
  Requires-Dist: httpx-sse (>=0.4.0,<0.5.0)
20
19
  Requires-Dist: httpx[http2] (>=0.27,<0.28)
21
20
  Requires-Dist: nanoid (>=2.0.0,<3.0.0)
@@ -23,7 +22,7 @@ Requires-Dist: pydantic (==2.10.4)
23
22
  Requires-Dist: pyyaml (>=6,<7)
24
23
  Requires-Dist: rich (>=13.9.2,<14.0.0)
25
24
  Requires-Dist: structlog (>=24.4.0,<25.0.0)
26
- Requires-Dist: tensorlake (>=0.1.9)
25
+ Requires-Dist: tensorlake (>=0.1.13)
27
26
  Requires-Dist: typer (>=0.12,<0.13)
28
27
  Project-URL: Repository, https://github.com/tensorlakeai/indexify
29
28
  Description-Content-Type: text/markdown
@@ -0,0 +1,25 @@
1
+ indexify/cli/cli.py,sha256=uZjcbl41cfBCYrXYp3ggplz3rI7enf-I8pwSYCeNsTo,11502
2
+ indexify/executor/README.md,sha256=ozC6_hMkhQQNVCMEpBxwiUALz6lwErPQxNxQfQDqnG4,2029
3
+ indexify/executor/api_objects.py,sha256=k5tKYxaWml0sSECoEDzamCYeJnlD7zO2M7E_qGwyMrg,1032
4
+ indexify/executor/downloader.py,sha256=a4f7M_Npfvy5Y-XLqmrPRUdPYvonl4qbK0dDw3EvpZ8,6460
5
+ indexify/executor/executor.py,sha256=np6qfwGrCAOmupefoIiGsznJcppZ62vagQuQId4L1Gg,5895
6
+ indexify/executor/function_executor/function_executor.py,sha256=00ILKA4kctsky4v3y26xispJh5Bl07H2VBN9fsaTbOA,5838
7
+ indexify/executor/function_executor/function_executor_state.py,sha256=_85dpaudYM0sekOqwjMxKGdK7MNQdTGUhHi67sqVHyY,2853
8
+ indexify/executor/function_executor/health_checker.py,sha256=mYR0WRL-gDFXoyaTBRBnnl9ZfJlL4d-RRe48HEZtU0g,2750
9
+ indexify/executor/function_executor/invocation_state_client.py,sha256=CfIcVKJZoFMQFOyi3R_dtMgHs5VcGo2V4FKH0or6n80,8586
10
+ indexify/executor/function_executor/server/client_configuration.py,sha256=gOywMus0cotlX6NKIadEJwvOmBE-LbGE_wvoMi5-HzY,994
11
+ indexify/executor/function_executor/server/function_executor_server.py,sha256=_DLivLDikupZusRk8gVWDk7fWPT9XjZ4un1yWSlOObs,883
12
+ indexify/executor/function_executor/server/function_executor_server_factory.py,sha256=pGbJMQfC5TNvyWOs6VDKdqd2PK5OHQh5_wSDP-E7DbI,1677
13
+ indexify/executor/function_executor/server/subprocess_function_executor_server.py,sha256=JekDOqF7oFD4J6zcN3xB0Dxd1cgpEXMOsb_rKZOeBlI,668
14
+ indexify/executor/function_executor/server/subprocess_function_executor_server_factory.py,sha256=FYExuYZZ7CUcznUobtnxvd2hVjUjpB9Dkav0FFcA0hM,4118
15
+ indexify/executor/function_executor/single_task_runner.py,sha256=asaBLQlY0P5KigA7waX3Lra1aWi8oFeNi7jVj5KvjDI,7764
16
+ indexify/executor/function_executor/task_input.py,sha256=wSrHR4m0juiGClQyeVdhRC37QzDt6Rrjq-ZXJkfBi9k,584
17
+ indexify/executor/function_executor/task_output.py,sha256=FLtqpmfhv6faAls0HCzyiZyyOjvENlrslpfl4Mz9JvI,1066
18
+ indexify/executor/runtime_probes.py,sha256=bo6Dq6AGZpJH099j0DHtVSDEH80tv3j9MXf3VXSx_p8,2182
19
+ indexify/executor/task_fetcher.py,sha256=hng1moUwRl4bUMwKum8eGgcAd9djU5PJqHT9hxFnhtU,2912
20
+ indexify/executor/task_reporter.py,sha256=m0JrXYnBw-wUqeZHdtewCW9H8ojtcphCbvAkGQRPe8g,6640
21
+ indexify/executor/task_runner.py,sha256=RSFeJYhQ_agXXPBm8u13HErSUPZGsCGwV1stgS7g258,5035
22
+ indexify-0.3.6.dist-info/METADATA,sha256=zvi_iL28kPeHzKrmJiuV928j9oKPuZ3mJAgJQZf9v8M,1334
23
+ indexify-0.3.6.dist-info/WHEEL,sha256=RaoafKOydTQ7I_I3JTrPCg6kUmTgtm4BornzOqyEfJ8,88
24
+ indexify-0.3.6.dist-info/entry_points.txt,sha256=GU9wmsgvN7nQw3N2X0PMYn1RSvF6CrhH9RuC2D8d3Gk,53
25
+ indexify-0.3.6.dist-info/RECORD,,
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ indexify-cli=indexify.cli.cli:app
3
+
@@ -1,18 +0,0 @@
1
- ## Overview
2
-
3
- Function Executor is a process with an API that allows to load and run a customer Function in Indexify.
4
- Each function run is a task. The tasks can be executed concurrently. The API client controls
5
- the desired concurrency. Killing the process allows to free all the resources that a loaded customer
6
- functon is using. This is helpful because the SDK doesn't provide any callbacks to customer code to free
7
- resources it's using. Even if there was such callback customer code still might misbehave.
8
-
9
- ## Deployment
10
-
11
- A Function Executor is created and destroyed by another component called Executor. It also calls the
12
- Function Executor APIs. The server is not expected to be deployed or managed manually by Indexify users
13
- as it's a low level component.
14
-
15
- ## Threat model
16
-
17
- Customer code is assumed to be not trusted. Function Executor must not obtain any credentials that grant
18
- access to resources not owned by the customer who owns the function.
@@ -1,53 +0,0 @@
1
- from typing import Optional
2
-
3
- from tensorlake.functions_sdk.data_objects import TensorlakeData
4
- from tensorlake.functions_sdk.object_serializer import get_serializer
5
-
6
- from ...proto.function_executor_pb2 import RunTaskRequest, SerializedObject
7
-
8
-
9
- class FunctionInputs:
10
- def __init__(
11
- self, input: TensorlakeData, init_value: Optional[TensorlakeData] = None
12
- ):
13
- self.input = input
14
- self.init_value = init_value
15
-
16
-
17
- class FunctionInputsLoader:
18
- def __init__(self, request: RunTaskRequest):
19
- self._request = request
20
-
21
- def load(self) -> FunctionInputs:
22
- return FunctionInputs(
23
- input=self._function_input(),
24
- init_value=self._accumulator_input(),
25
- )
26
-
27
- def _function_input(self) -> TensorlakeData:
28
- return _to_indexify_data(
29
- self._request.graph_invocation_id, self._request.function_input
30
- )
31
-
32
- def _accumulator_input(self) -> Optional[TensorlakeData]:
33
- return (
34
- _to_indexify_data(
35
- self._request.graph_invocation_id, self._request.function_init_value
36
- )
37
- if self._request.HasField("function_init_value")
38
- else None
39
- )
40
-
41
-
42
- def _to_indexify_data(
43
- input_id: str, serialized_object: SerializedObject
44
- ) -> TensorlakeData:
45
- return TensorlakeData(
46
- input_id=input_id,
47
- payload=(
48
- serialized_object.bytes
49
- if serialized_object.HasField("bytes")
50
- else serialized_object.string
51
- ),
52
- encoder=get_serializer(serialized_object.content_type).encoding_type,
53
- )
@@ -1,126 +0,0 @@
1
- import io
2
- import sys
3
- import traceback
4
- from contextlib import redirect_stderr, redirect_stdout
5
- from typing import Any, Union
6
-
7
- from tensorlake.functions_sdk.functions import (
8
- FunctionCallResult,
9
- GraphInvocationContext,
10
- RouterCallResult,
11
- TensorlakeCompute,
12
- TensorlakeFunctionWrapper,
13
- TensorlakeRouter,
14
- )
15
- from tensorlake.functions_sdk.invocation_state.invocation_state import InvocationState
16
-
17
- from ...proto.function_executor_pb2 import RunTaskRequest, RunTaskResponse
18
- from .function_inputs_loader import FunctionInputs, FunctionInputsLoader
19
- from .response_helper import ResponseHelper
20
-
21
-
22
- class Handler:
23
- def __init__(
24
- self,
25
- request: RunTaskRequest,
26
- graph_name: str,
27
- graph_version: str,
28
- function_name: str,
29
- function: Union[TensorlakeCompute, TensorlakeCompute],
30
- invocation_state: InvocationState,
31
- logger: Any,
32
- ):
33
- self._function_name: str = function_name
34
- self._logger = logger.bind(
35
- graph_invocation_id=request.graph_invocation_id,
36
- task_id=request.task_id,
37
- )
38
- self._input_loader = FunctionInputsLoader(request)
39
- self._response_helper = ResponseHelper(task_id=request.task_id)
40
- # TODO: use files for stdout, stderr capturing. This puts a natural and thus reasonable
41
- # rate limit on the rate of writes and allows to not consume expensive memory for function logs.
42
- self._func_stdout: io.StringIO = io.StringIO()
43
- self._func_stderr: io.StringIO = io.StringIO()
44
-
45
- self._function_wrapper: TensorlakeFunctionWrapper = TensorlakeFunctionWrapper(
46
- indexify_function=function,
47
- context=GraphInvocationContext(
48
- invocation_id=request.graph_invocation_id,
49
- graph_name=graph_name,
50
- graph_version=graph_version,
51
- invocation_state=invocation_state,
52
- ),
53
- )
54
-
55
- def run(self) -> RunTaskResponse:
56
- """Runs the task.
57
-
58
- Raises an exception if our own code failed, customer function failure doesn't result in any exception.
59
- Details of customer function failure are returned in the response.
60
- """
61
- self._logger.info("running function")
62
- inputs: FunctionInputs = self._input_loader.load()
63
- self._flush_logs()
64
- return self._run_func_safe_and_captured(inputs)
65
-
66
- def _run_func_safe_and_captured(self, inputs: FunctionInputs) -> RunTaskResponse:
67
- """Runs the customer function while capturing what happened in it.
68
-
69
- Function stdout and stderr are captured so they don't get into Function Executor process stdout
70
- and stderr. Never throws an Exception. Caller can determine if the function succeeded
71
- using the response.
72
- """
73
- try:
74
- with redirect_stdout(self._func_stdout), redirect_stderr(self._func_stderr):
75
- return self._run_func(inputs)
76
- except Exception:
77
- return self._response_helper.failure_response(
78
- message=traceback.format_exc(),
79
- stdout=self._func_stdout.getvalue(),
80
- stderr=self._func_stderr.getvalue(),
81
- )
82
-
83
- def _run_func(self, inputs: FunctionInputs) -> RunTaskResponse:
84
- if _is_router(self._function_wrapper):
85
- result: RouterCallResult = self._function_wrapper.invoke_router(
86
- self._function_name, inputs.input
87
- )
88
- return self._response_helper.router_response(
89
- result=result,
90
- stdout=self._func_stdout.getvalue(),
91
- stderr=self._func_stderr.getvalue(),
92
- )
93
- else:
94
- result: FunctionCallResult = self._function_wrapper.invoke_fn_ser(
95
- self._function_name, inputs.input, inputs.init_value
96
- )
97
- return self._response_helper.function_response(
98
- result=result,
99
- is_reducer=_func_is_reducer(self._function_wrapper),
100
- stdout=self._func_stdout.getvalue(),
101
- stderr=self._func_stderr.getvalue(),
102
- )
103
-
104
- def _flush_logs(self) -> None:
105
- # Flush any logs buffered in memory before running the function with stdout, stderr capture.
106
- # Otherwise our logs logged before this point will end up in the function's stdout.
107
- # structlog.PrintLogger uses print function. This is why flushing with print works.
108
- print("", flush=True)
109
- sys.stdout.flush()
110
- sys.stderr.flush()
111
-
112
-
113
- def _is_router(func_wrapper: TensorlakeFunctionWrapper) -> bool:
114
- """Determines if the function is a router.
115
-
116
- A function is a router if it is an instance of TensorlakeRouter or if it is an TensorlakeRouter class.
117
- """
118
- return str(
119
- type(func_wrapper.indexify_function)
120
- ) == "<class 'tensorlake.functions_sdk.functions.TensorlakeRouter'>" or isinstance(
121
- func_wrapper.indexify_function, TensorlakeRouter
122
- )
123
-
124
-
125
- def _func_is_reducer(func_wrapper: TensorlakeFunctionWrapper) -> bool:
126
- return func_wrapper.indexify_function.accumulate is not None
@@ -1,26 +0,0 @@
1
- from typing import Any
2
-
3
- from ...proto.function_executor_pb2 import RunTaskRequest
4
- from ...proto.message_validator import MessageValidator
5
-
6
-
7
- class RequestValidator:
8
- def __init__(self, request: RunTaskRequest):
9
- self._request = request
10
- self._message_validator = MessageValidator(request)
11
-
12
- def check(self):
13
- """Validates the request.
14
-
15
- Raises: ValueError: If the request is invalid.
16
- """
17
- (
18
- self._message_validator.required_field("namespace")
19
- .required_field("graph_name")
20
- .required_field("graph_version")
21
- .required_field("function_name")
22
- .required_field("graph_invocation_id")
23
- .required_field("task_id")
24
- .required_serialized_object("function_input")
25
- .optional_serialized_object("function_init_value")
26
- )