indexify 0.4.21__py3-none-any.whl → 0.4.22__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.
- indexify/executor/function_executor_controller/metrics/run_task.py +5 -4
- indexify/executor/function_executor_controller/run_task.py +140 -48
- indexify/executor/function_executor_controller/task_output.py +17 -0
- {indexify-0.4.21.dist-info → indexify-0.4.22.dist-info}/METADATA +3 -3
- {indexify-0.4.21.dist-info → indexify-0.4.22.dist-info}/RECORD +7 -7
- {indexify-0.4.21.dist-info → indexify-0.4.22.dist-info}/WHEEL +0 -0
- {indexify-0.4.21.dist-info → indexify-0.4.22.dist-info}/entry_points.txt +0 -0
@@ -6,23 +6,24 @@ from indexify.executor.monitoring.metrics import (
|
|
6
6
|
|
7
7
|
metric_function_executor_run_task_rpcs: prometheus_client.Counter = (
|
8
8
|
prometheus_client.Counter(
|
9
|
-
"function_executor_run_task_rpcs",
|
9
|
+
"function_executor_run_task_rpcs",
|
10
|
+
"Number of Function Executor run task lifecycle RPC sequences",
|
10
11
|
)
|
11
12
|
)
|
12
13
|
metric_function_executor_run_task_rpc_errors: prometheus_client.Counter = (
|
13
14
|
prometheus_client.Counter(
|
14
15
|
"function_executor_run_task_rpc_errors",
|
15
|
-
"Number of Function Executor run task RPC errors",
|
16
|
+
"Number of Function Executor run task lifecycle RPC errors",
|
16
17
|
)
|
17
18
|
)
|
18
19
|
metric_function_executor_run_task_rpc_latency: prometheus_client.Histogram = (
|
19
20
|
latency_metric_for_customer_controlled_operation(
|
20
|
-
"function_executor_run_task_rpc", "Function Executor run task RPC"
|
21
|
+
"function_executor_run_task_rpc", "Function Executor run task lifecycle RPC"
|
21
22
|
)
|
22
23
|
)
|
23
24
|
metric_function_executor_run_task_rpcs_in_progress: prometheus_client.Gauge = (
|
24
25
|
prometheus_client.Gauge(
|
25
26
|
"function_executor_run_task_rpcs_in_progress",
|
26
|
-
"Number of Function Executor run task RPCs in progress",
|
27
|
+
"Number of Function Executor run task lifecycle RPCs in progress",
|
27
28
|
)
|
28
29
|
)
|
@@ -6,9 +6,13 @@ from typing import Any, Optional
|
|
6
6
|
|
7
7
|
import grpc
|
8
8
|
from tensorlake.function_executor.proto.function_executor_pb2 import (
|
9
|
-
|
10
|
-
|
9
|
+
AwaitTaskProgress,
|
10
|
+
AwaitTaskRequest,
|
11
|
+
CreateTaskRequest,
|
12
|
+
DeleteTaskRequest,
|
13
|
+
FunctionInputs,
|
11
14
|
SerializedObject,
|
15
|
+
Task,
|
12
16
|
)
|
13
17
|
from tensorlake.function_executor.proto.function_executor_pb2 import (
|
14
18
|
TaskFailureReason as FETaskFailureReason,
|
@@ -16,6 +20,9 @@ from tensorlake.function_executor.proto.function_executor_pb2 import (
|
|
16
20
|
from tensorlake.function_executor.proto.function_executor_pb2 import (
|
17
21
|
TaskOutcomeCode as FETaskOutcomeCode,
|
18
22
|
)
|
23
|
+
from tensorlake.function_executor.proto.function_executor_pb2 import (
|
24
|
+
TaskResult,
|
25
|
+
)
|
19
26
|
from tensorlake.function_executor.proto.function_executor_pb2_grpc import (
|
20
27
|
FunctionExecutorStub,
|
21
28
|
)
|
@@ -44,6 +51,9 @@ _ENABLE_INJECT_TASK_CANCELLATIONS = (
|
|
44
51
|
os.getenv("INDEXIFY_INJECT_TASK_CANCELLATIONS", "0") == "1"
|
45
52
|
)
|
46
53
|
|
54
|
+
_CREATE_TASK_TIMEOUT_SECS = 5
|
55
|
+
_DELETE_TASK_TIMEOUT_SECS = 5
|
56
|
+
|
47
57
|
|
48
58
|
async def run_task_on_function_executor(
|
49
59
|
task_info: TaskInfo, function_executor: FunctionExecutor, logger: Any
|
@@ -53,21 +63,21 @@ async def run_task_on_function_executor(
|
|
53
63
|
Doesn't raise any exceptions.
|
54
64
|
"""
|
55
65
|
logger = logger.bind(module=__name__)
|
56
|
-
|
66
|
+
task = Task(
|
67
|
+
task_id=task_info.allocation.task.id,
|
57
68
|
namespace=task_info.allocation.task.namespace,
|
58
69
|
graph_name=task_info.allocation.task.graph_name,
|
59
70
|
graph_version=task_info.allocation.task.graph_version,
|
60
71
|
function_name=task_info.allocation.task.function_name,
|
61
72
|
graph_invocation_id=task_info.allocation.task.graph_invocation_id,
|
62
|
-
task_id=task_info.allocation.task.id,
|
63
73
|
allocation_id=task_info.allocation.allocation_id,
|
64
|
-
function_input=task_info.input,
|
74
|
+
request=FunctionInputs(function_input=task_info.input),
|
65
75
|
)
|
66
76
|
# Don't keep the input in memory after we started running the task.
|
67
77
|
task_info.input = None
|
68
78
|
|
69
79
|
if task_info.init_value is not None:
|
70
|
-
request.function_init_value.CopyFrom(task_info.init_value)
|
80
|
+
task.request.function_init_value.CopyFrom(task_info.init_value)
|
71
81
|
# Don't keep the init value in memory after we started running the task.
|
72
82
|
task_info.init_value = None
|
73
83
|
|
@@ -78,50 +88,75 @@ async def run_task_on_function_executor(
|
|
78
88
|
|
79
89
|
metric_function_executor_run_task_rpcs.inc()
|
80
90
|
metric_function_executor_run_task_rpcs_in_progress.inc()
|
81
|
-
start_time = time.monotonic()
|
82
91
|
# Not None if the Function Executor should be terminated after running the task.
|
83
92
|
function_executor_termination_reason: Optional[
|
84
93
|
FunctionExecutorTerminationReason
|
85
94
|
] = None
|
86
|
-
|
95
|
+
|
96
|
+
# NB: We start this timer before invoking the first RPC, since
|
97
|
+
# user code should be executing by the time the create_task() RPC
|
98
|
+
# returns, so not attributing the task management RPC overhead to
|
99
|
+
# the user would open a possibility for abuse. (This is somewhat
|
100
|
+
# mitigated by the fact that these RPCs should have a very low
|
101
|
+
# overhead.)
|
102
|
+
execution_start_time: Optional[float] = time.monotonic()
|
87
103
|
|
88
104
|
# If this RPC failed due to customer code crashing the server we won't be
|
89
105
|
# able to detect this. We'll treat this as our own error for now and thus
|
90
106
|
# let the AioRpcError to be raised here.
|
91
107
|
timeout_sec = task_info.allocation.task.timeout_ms / 1000.0
|
92
108
|
try:
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
request, timeout=timeout_sec
|
97
|
-
)
|
98
|
-
task_info.output = _task_output_from_function_executor_response(
|
109
|
+
task_result = await _run_task_rpcs(task, function_executor, timeout_sec)
|
110
|
+
|
111
|
+
task_info.output = _task_output_from_function_executor_result(
|
99
112
|
allocation=task_info.allocation,
|
100
|
-
|
113
|
+
result=task_result,
|
101
114
|
execution_start_time=execution_start_time,
|
102
115
|
execution_end_time=time.monotonic(),
|
103
116
|
logger=logger,
|
104
117
|
)
|
118
|
+
except asyncio.TimeoutError:
|
119
|
+
# This is an await_task() RPC timeout - we're not getting
|
120
|
+
# progress messages or a task completion.
|
121
|
+
function_executor_termination_reason = (
|
122
|
+
FunctionExecutorTerminationReason.FUNCTION_EXECUTOR_TERMINATION_REASON_FUNCTION_TIMEOUT
|
123
|
+
)
|
124
|
+
task_info.output = TaskOutput.function_timeout(
|
125
|
+
allocation=task_info.allocation,
|
126
|
+
timeout_sec=timeout_sec,
|
127
|
+
execution_start_time=execution_start_time,
|
128
|
+
execution_end_time=time.monotonic(),
|
129
|
+
)
|
105
130
|
except grpc.aio.AioRpcError as e:
|
131
|
+
# This indicates some sort of problem communicating with the FE.
|
132
|
+
#
|
133
|
+
# NB: We charge the user in these situations: code within the
|
134
|
+
# FE is not isolated, so not charging would enable abuse.
|
135
|
+
#
|
136
|
+
# This is an unexpected situation, though, so we make sure to
|
137
|
+
# log the situation for further investigation.
|
138
|
+
|
139
|
+
function_executor_termination_reason = (
|
140
|
+
FunctionExecutorTerminationReason.FUNCTION_EXECUTOR_TERMINATION_REASON_UNHEALTHY
|
141
|
+
)
|
142
|
+
metric_function_executor_run_task_rpc_errors.inc()
|
143
|
+
|
106
144
|
if e.code() == grpc.StatusCode.DEADLINE_EXCEEDED:
|
107
|
-
#
|
108
|
-
|
109
|
-
|
110
|
-
)
|
111
|
-
task_info.output = TaskOutput.function_timeout(
|
112
|
-
allocation=task_info.allocation,
|
113
|
-
timeout_sec=timeout_sec,
|
114
|
-
execution_start_time=execution_start_time,
|
115
|
-
execution_end_time=time.monotonic(),
|
116
|
-
)
|
145
|
+
# This is either a create_task() RPC timeout or a
|
146
|
+
# delete_task() RPC timeout; either suggests that the FE
|
147
|
+
# is unhealthy.
|
148
|
+
logger.error("task management RPC execution deadline exceeded", exc_info=e)
|
117
149
|
else:
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
150
|
+
# This is a status from an unsuccessful RPC; this
|
151
|
+
# shouldn't happen, but we handle it.
|
152
|
+
logger.error("task management RPC failed", exc_info=e)
|
153
|
+
|
154
|
+
task_info.output = TaskOutput.function_executor_unresponsive(
|
155
|
+
allocation=task_info.allocation,
|
156
|
+
execution_start_time=execution_start_time,
|
157
|
+
execution_end_time=time.monotonic(),
|
158
|
+
)
|
159
|
+
|
125
160
|
except asyncio.CancelledError:
|
126
161
|
# The task is still running in FE, we only cancelled the client-side RPC.
|
127
162
|
function_executor_termination_reason = (
|
@@ -133,15 +168,20 @@ async def run_task_on_function_executor(
|
|
133
168
|
execution_end_time=time.monotonic(),
|
134
169
|
)
|
135
170
|
except Exception as e:
|
136
|
-
|
137
|
-
|
171
|
+
# This is an unexpected exception; we believe that this
|
172
|
+
# indicates an internal error.
|
173
|
+
logger.error(
|
174
|
+
"Unexpected internal error during task lifecycle RPC sequence", exc_info=e
|
175
|
+
)
|
138
176
|
task_info.output = TaskOutput.internal_error(
|
139
177
|
allocation=task_info.allocation,
|
140
178
|
execution_start_time=execution_start_time,
|
141
179
|
execution_end_time=time.monotonic(),
|
142
180
|
)
|
143
181
|
|
144
|
-
metric_function_executor_run_task_rpc_latency.observe(
|
182
|
+
metric_function_executor_run_task_rpc_latency.observe(
|
183
|
+
time.monotonic() - execution_start_time
|
184
|
+
)
|
145
185
|
metric_function_executor_run_task_rpcs_in_progress.dec()
|
146
186
|
|
147
187
|
function_executor.invocation_state_client().remove_task_to_invocation_id_entry(
|
@@ -171,26 +211,78 @@ async def run_task_on_function_executor(
|
|
171
211
|
)
|
172
212
|
|
173
213
|
|
174
|
-
def
|
214
|
+
async def _run_task_rpcs(
|
215
|
+
task: Task, function_executor: FunctionExecutor, timeout_sec: float
|
216
|
+
) -> TaskResult:
|
217
|
+
"""Runs the task, returning the result, reporting errors via exceptions."""
|
218
|
+
|
219
|
+
response: AwaitTaskProgress
|
220
|
+
channel: grpc.aio.Channel = function_executor.channel()
|
221
|
+
fe_stub = FunctionExecutorStub(channel)
|
222
|
+
|
223
|
+
# Create task with timeout
|
224
|
+
await fe_stub.create_task(
|
225
|
+
CreateTaskRequest(task=task), timeout=_CREATE_TASK_TIMEOUT_SECS
|
226
|
+
)
|
227
|
+
|
228
|
+
# Await task with timeout resets on each response
|
229
|
+
await_rpc = fe_stub.await_task(AwaitTaskRequest(task_id=task.task_id))
|
230
|
+
|
231
|
+
try:
|
232
|
+
while True:
|
233
|
+
# Wait for next response with fresh timeout each time
|
234
|
+
response = await asyncio.wait_for(await_rpc.read(), timeout=timeout_sec)
|
235
|
+
if response.WhichOneof("response") == "task_result":
|
236
|
+
# We're done waiting.
|
237
|
+
break
|
238
|
+
|
239
|
+
# NB: We don't actually check for other message types
|
240
|
+
# here; any message from the FE is treated as an
|
241
|
+
# indication that it's making forward progress.
|
242
|
+
|
243
|
+
if response == grpc.aio.EOF:
|
244
|
+
# Protocol error: we should get a task_result before
|
245
|
+
# we see the RPC complete.
|
246
|
+
raise grpc.aio.AioRpcError(
|
247
|
+
grpc.StatusCode.CANCELLED,
|
248
|
+
None,
|
249
|
+
None,
|
250
|
+
"Function Executor didn't return function/task alloc response",
|
251
|
+
)
|
252
|
+
finally:
|
253
|
+
# Cancel the outstanding RPC to ensure any resources in use
|
254
|
+
# are cleaned up; note that this is idempotent (in case the
|
255
|
+
# RPC has already completed).
|
256
|
+
await_rpc.cancel()
|
257
|
+
|
258
|
+
# Delete task with timeout
|
259
|
+
await fe_stub.delete_task(
|
260
|
+
DeleteTaskRequest(task_id=task.task_id), timeout=_DELETE_TASK_TIMEOUT_SECS
|
261
|
+
)
|
262
|
+
|
263
|
+
return response.task_result
|
264
|
+
|
265
|
+
|
266
|
+
def _task_output_from_function_executor_result(
|
175
267
|
allocation: TaskAllocation,
|
176
|
-
|
268
|
+
result: TaskResult,
|
177
269
|
execution_start_time: Optional[float],
|
178
270
|
execution_end_time: Optional[float],
|
179
271
|
logger: Any,
|
180
272
|
) -> TaskOutput:
|
181
|
-
response_validator = MessageValidator(
|
273
|
+
response_validator = MessageValidator(result)
|
182
274
|
response_validator.required_field("stdout")
|
183
275
|
response_validator.required_field("stderr")
|
184
276
|
response_validator.required_field("outcome_code")
|
185
277
|
|
186
278
|
metrics = TaskMetrics(counters={}, timers={})
|
187
|
-
if
|
279
|
+
if result.HasField("metrics"):
|
188
280
|
# Can be None if e.g. function failed.
|
189
|
-
metrics.counters = dict(
|
190
|
-
metrics.timers = dict(
|
281
|
+
metrics.counters = dict(result.metrics.counters)
|
282
|
+
metrics.timers = dict(result.metrics.timers)
|
191
283
|
|
192
284
|
outcome_code: TaskOutcomeCode = _to_task_outcome_code(
|
193
|
-
|
285
|
+
result.outcome_code, logger=logger
|
194
286
|
)
|
195
287
|
failure_reason: Optional[TaskFailureReason] = None
|
196
288
|
invocation_error_output: Optional[SerializedObject] = None
|
@@ -198,11 +290,11 @@ def _task_output_from_function_executor_response(
|
|
198
290
|
if outcome_code == TaskOutcomeCode.TASK_OUTCOME_CODE_FAILURE:
|
199
291
|
response_validator.required_field("failure_reason")
|
200
292
|
failure_reason: Optional[TaskFailureReason] = _to_task_failure_reason(
|
201
|
-
|
293
|
+
result.failure_reason, logger
|
202
294
|
)
|
203
295
|
if failure_reason == TaskFailureReason.TASK_FAILURE_REASON_INVOCATION_ERROR:
|
204
296
|
response_validator.required_field("invocation_error_output")
|
205
|
-
invocation_error_output =
|
297
|
+
invocation_error_output = result.invocation_error_output
|
206
298
|
|
207
299
|
if _ENABLE_INJECT_TASK_CANCELLATIONS:
|
208
300
|
logger.warning("injecting cancellation failure for the task allocation")
|
@@ -217,10 +309,10 @@ def _task_output_from_function_executor_response(
|
|
217
309
|
outcome_code=outcome_code,
|
218
310
|
failure_reason=failure_reason,
|
219
311
|
invocation_error_output=invocation_error_output,
|
220
|
-
function_outputs=
|
221
|
-
next_functions=
|
222
|
-
stdout=
|
223
|
-
stderr=
|
312
|
+
function_outputs=result.function_outputs,
|
313
|
+
next_functions=result.next_functions,
|
314
|
+
stdout=result.stdout,
|
315
|
+
stderr=result.stderr,
|
224
316
|
metrics=metrics,
|
225
317
|
execution_start_time=execution_start_time,
|
226
318
|
execution_end_time=execution_end_time,
|
@@ -95,6 +95,23 @@ class TaskOutput:
|
|
95
95
|
execution_end_time=execution_end_time,
|
96
96
|
)
|
97
97
|
|
98
|
+
@classmethod
|
99
|
+
def function_executor_unresponsive(
|
100
|
+
cls,
|
101
|
+
allocation: TaskAllocation,
|
102
|
+
execution_start_time: Optional[float],
|
103
|
+
execution_end_time: Optional[float],
|
104
|
+
) -> "TaskOutput":
|
105
|
+
"""Creates a TaskOutput for an unresponsive FE."""
|
106
|
+
# Task stdout, stderr is not available.
|
107
|
+
return TaskOutput(
|
108
|
+
allocation=allocation,
|
109
|
+
outcome_code=TaskOutcomeCode.TASK_OUTCOME_CODE_FAILURE,
|
110
|
+
failure_reason=TaskFailureReason.TASK_FAILURE_REASON_FUNCTION_ERROR,
|
111
|
+
execution_start_time=execution_start_time,
|
112
|
+
execution_end_time=execution_end_time,
|
113
|
+
)
|
114
|
+
|
98
115
|
@classmethod
|
99
116
|
def task_cancelled(
|
100
117
|
cls,
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: indexify
|
3
|
-
Version: 0.4.
|
3
|
+
Version: 0.4.22
|
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,7 +14,7 @@ Classifier: Programming Language :: Python :: 3.11
|
|
14
14
|
Classifier: Programming Language :: Python :: 3.12
|
15
15
|
Classifier: Programming Language :: Python :: 3.13
|
16
16
|
Requires-Dist: aiohttp (>=3.12.14,<4.0.0)
|
17
|
-
Requires-Dist: boto3 (>=1.39.
|
17
|
+
Requires-Dist: boto3 (>=1.39.15,<2.0.0)
|
18
18
|
Requires-Dist: docker (>=7.1.0,<8.0.0)
|
19
19
|
Requires-Dist: httpx[http2] (==0.27.2)
|
20
20
|
Requires-Dist: nanoid (>=2.0.0,<3.0.0)
|
@@ -22,7 +22,7 @@ Requires-Dist: prometheus-client (>=0.22.1,<0.23.0)
|
|
22
22
|
Requires-Dist: psutil (>=7.0.0,<8.0.0)
|
23
23
|
Requires-Dist: pydantic (>=2.11,<3.0)
|
24
24
|
Requires-Dist: requests (>=2.32.4,<3.0.0)
|
25
|
-
Requires-Dist: tensorlake (==0.2.
|
25
|
+
Requires-Dist: tensorlake (==0.2.37)
|
26
26
|
Requires-Dist: urllib3 (>=2.5.0,<3.0.0)
|
27
27
|
Project-URL: Repository, https://github.com/tensorlakeai/indexify
|
28
28
|
Description-Content-Type: text/markdown
|
@@ -34,12 +34,12 @@ indexify/executor/function_executor_controller/message_validators.py,sha256=aNiZ
|
|
34
34
|
indexify/executor/function_executor_controller/metrics/completed_task_metrics.py,sha256=53EGBCLwCEV-RBBeyLPTElrtcveaEM0Fwxs9NmC1Hn8,2724
|
35
35
|
indexify/executor/function_executor_controller/metrics/downloads.py,sha256=KOVTE2OZPCewnCooPyCK1maKe9ddMPvBFp7D_igqugQ,2708
|
36
36
|
indexify/executor/function_executor_controller/metrics/function_executor_controller.py,sha256=gyIZHbdsNSnrA6z4WDaRAunNolFrbbg1pa8JaL_ODNE,2666
|
37
|
-
indexify/executor/function_executor_controller/metrics/run_task.py,sha256=
|
37
|
+
indexify/executor/function_executor_controller/metrics/run_task.py,sha256=ZFv_nw5_pKUJoTaavSyzdglQKW4uvC2XyK8S6xi9xLQ,1064
|
38
38
|
indexify/executor/function_executor_controller/metrics/upload_task_output.py,sha256=Ppf8NujCNbQzFelJiuh8Sutcjty7hnkFz1dWQLYIgQI,1464
|
39
39
|
indexify/executor/function_executor_controller/prepare_task.py,sha256=AKbo_H_5pOKdxFKKkzdOb1WhQ0XT-4Qm9D3iIsukyMU,1247
|
40
|
-
indexify/executor/function_executor_controller/run_task.py,sha256=
|
40
|
+
indexify/executor/function_executor_controller/run_task.py,sha256=2qpP6YwKoi_sEWDJIJ43EyBgO3Z3yYDVFpncB01DQf4,14586
|
41
41
|
indexify/executor/function_executor_controller/task_info.py,sha256=ZEdypd8QVmYbrLt1186Ed9YEQwrO0Sx_hKH0QLg1DVY,1181
|
42
|
-
indexify/executor/function_executor_controller/task_output.py,sha256=
|
42
|
+
indexify/executor/function_executor_controller/task_output.py,sha256=krwJC8GgVWYM_b_v5dHtVytsH3mHz3Qi7REiEM2D7nA,7358
|
43
43
|
indexify/executor/function_executor_controller/terminate_function_executor.py,sha256=YLDlKoanfUBcy7A9ydCYdUsDwApjcTTn1o4tjNVN_QA,1281
|
44
44
|
indexify/executor/function_executor_controller/upload_task_output.py,sha256=fEZm5eodx5rNLQYFhmdkMDD9qjX3_wKo64x4aUKTu34,10403
|
45
45
|
indexify/executor/host_resources/host_resources.py,sha256=eUyP05EX7QdOtQ5vbX_KCpvnBS2B7fl06UWeF9Oigns,3813
|
@@ -64,7 +64,7 @@ indexify/proto/executor_api.proto,sha256=Zvx5eTz_QFHCX2udzOwHZD-ncJfDlXeKVbNBhsa
|
|
64
64
|
indexify/proto/executor_api_pb2.py,sha256=iSiO2Aw7vQV5Jp7IoSeIC8q32WjE-s-AZ0Hc5MwI0uI,16266
|
65
65
|
indexify/proto/executor_api_pb2.pyi,sha256=oReajQdT2yNPS4kZd3gDMBdbseHv-gU15fpeT0v3ZBs,22819
|
66
66
|
indexify/proto/executor_api_pb2_grpc.py,sha256=u9GEQV4nm_GvApRxjVo806CkgBMBVReb5IVrcaDaliY,7520
|
67
|
-
indexify-0.4.
|
68
|
-
indexify-0.4.
|
69
|
-
indexify-0.4.
|
70
|
-
indexify-0.4.
|
67
|
+
indexify-0.4.22.dist-info/METADATA,sha256=poiBPNliE54kWJyR5_FSISz-gWYYn6wXIzmgmn-Es3k,1354
|
68
|
+
indexify-0.4.22.dist-info/WHEEL,sha256=RaoafKOydTQ7I_I3JTrPCg6kUmTgtm4BornzOqyEfJ8,88
|
69
|
+
indexify-0.4.22.dist-info/entry_points.txt,sha256=rMJqbE5KPZIXTPIfAtVIM4zpUElqYVgEYd6i7N23zzg,49
|
70
|
+
indexify-0.4.22.dist-info/RECORD,,
|
File without changes
|
File without changes
|