flock-core 0.4.513__py3-none-any.whl → 0.4.515__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.
Potentially problematic release.
This version of flock-core might be problematic. Click here for more details.
- flock/core/execution/opik_executor.py +103 -0
- flock/core/flock_factory.py +1 -2
- flock/core/interpreter/python_interpreter.py +87 -81
- flock/core/mixin/dspy_integration.py +1 -1
- flock/core/util/input_resolver.py +1 -1
- flock/tools/code_tools.py +111 -0
- flock/tools/file_tools.py +10 -1
- flock/webapp/app/api/execution.py +1 -1
- flock/webapp/app/main.py +1 -1
- {flock_core-0.4.513.dist-info → flock_core-0.4.515.dist-info}/METADATA +4 -1
- {flock_core-0.4.513.dist-info → flock_core-0.4.515.dist-info}/RECORD +15 -14
- /flock/core/util/{spliter.py → splitter.py} +0 -0
- {flock_core-0.4.513.dist-info → flock_core-0.4.515.dist-info}/WHEEL +0 -0
- {flock_core-0.4.513.dist-info → flock_core-0.4.515.dist-info}/entry_points.txt +0 -0
- {flock_core-0.4.513.dist-info → flock_core-0.4.515.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# src/flock/core/execution/evaluation_processor.py
|
|
2
|
+
"""Contains the EvaluationProcessor class responsible for evaluating Flock agents
|
|
3
|
+
against datasets using various metrics.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import (
|
|
8
|
+
TYPE_CHECKING,
|
|
9
|
+
Any,
|
|
10
|
+
Union,
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
from opik import Opik
|
|
14
|
+
from pandas import DataFrame
|
|
15
|
+
|
|
16
|
+
# Conditional pandas import
|
|
17
|
+
try:
|
|
18
|
+
import pandas as pd
|
|
19
|
+
|
|
20
|
+
PANDAS_AVAILABLE = True
|
|
21
|
+
except ImportError:
|
|
22
|
+
pd = None # type: ignore
|
|
23
|
+
PANDAS_AVAILABLE = False
|
|
24
|
+
|
|
25
|
+
# Box for results
|
|
26
|
+
from datasets import Dataset as HFDataset
|
|
27
|
+
|
|
28
|
+
from flock.core.evaluation.utils import (
|
|
29
|
+
normalize_dataset,
|
|
30
|
+
# Import metric calculation/aggregation helpers
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
# Flock core imports
|
|
34
|
+
from flock.core.logging.logging import get_logger
|
|
35
|
+
|
|
36
|
+
if TYPE_CHECKING:
|
|
37
|
+
from flock.core.flock import Flock
|
|
38
|
+
from flock.core.flock_agent import FlockAgent
|
|
39
|
+
# Conditional types
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
logger = get_logger("execution.opik")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class OpikExecutor:
|
|
46
|
+
"""Handles the evaluation of Flock agents against datasets."""
|
|
47
|
+
|
|
48
|
+
def __init__(self, flock_instance: "Flock"):
|
|
49
|
+
"""Initializes the EvaluationProcessor.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
flock_instance: The Flock instance this processor will use.
|
|
53
|
+
"""
|
|
54
|
+
self.flock = flock_instance
|
|
55
|
+
|
|
56
|
+
async def evaluate_with_opik(
|
|
57
|
+
self,
|
|
58
|
+
dataset: str | Path | list[dict[str, Any]] | DataFrame | HFDataset,
|
|
59
|
+
start_agent: Union["FlockAgent", str],
|
|
60
|
+
input_mapping: dict[str, str],
|
|
61
|
+
answer_mapping: dict[str, str],) -> DataFrame | list[dict[str, Any]]:
|
|
62
|
+
"""Evaluates the Flock's performance against a dataset asynchronously."""
|
|
63
|
+
logger.info(f"Evaluating Flock's performance against dataset: {dataset}")
|
|
64
|
+
|
|
65
|
+
# Evaluation task
|
|
66
|
+
def evaluation_task(dataset_item):
|
|
67
|
+
flock_result = self.flock.run(start_agent=start_agent, input=dataset_item, box_result=False)
|
|
68
|
+
|
|
69
|
+
result = {
|
|
70
|
+
"input": dataset_item.get("test"),
|
|
71
|
+
"output": flock_result.get("answer"),
|
|
72
|
+
"context": ["placeholder string"]
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return result
|
|
76
|
+
|
|
77
|
+
start_agent_name = (
|
|
78
|
+
start_agent.name if hasattr(start_agent, "name") else start_agent
|
|
79
|
+
)
|
|
80
|
+
dataset_name = str(dataset)
|
|
81
|
+
|
|
82
|
+
# --- 1. Normalize Dataset ---
|
|
83
|
+
try:
|
|
84
|
+
df = normalize_dataset(dataset) # Uses helper
|
|
85
|
+
if df is None or df.empty:
|
|
86
|
+
raise ValueError(
|
|
87
|
+
"Provided dataset is empty or could not be normalized."
|
|
88
|
+
)
|
|
89
|
+
logger.info(f"Normalized dataset with {len(df)} items.")
|
|
90
|
+
except Exception as e:
|
|
91
|
+
logger.error(
|
|
92
|
+
f"Failed to load or normalize dataset: {e}", exc_info=True
|
|
93
|
+
)
|
|
94
|
+
raise ValueError(f"Dataset processing failed: {e}") from e
|
|
95
|
+
|
|
96
|
+
logger.info(f"type(df): {type(df)}") # ➜ <class 'pandas.core.frame.DataFrame'>
|
|
97
|
+
logger.info(f"df.shape: {df.shape}") # e.g. (123456, N_COLUMNS+2)
|
|
98
|
+
logger.info(f"df['split'].value_counts(): {df['split'].value_counts()}")
|
|
99
|
+
logger.info(f"df['config'].unique(): {df['config'].unique()}")
|
|
100
|
+
client = Opik()
|
|
101
|
+
dataset = client.get_or_create_dataset(name=dataset_name)
|
|
102
|
+
dataset.insert_from_pandas(dataframe=df, ignore_keys=["source"])
|
|
103
|
+
logger.info(f"Imported dataset to Opik")
|
flock/core/flock_factory.py
CHANGED
|
@@ -472,7 +472,6 @@ class FlockFactory:
|
|
|
472
472
|
schedule_expression: str, # e.g., "every 1h", "0 0 * * *"
|
|
473
473
|
description: str | Callable[..., str] | None = None,
|
|
474
474
|
model: str | Callable[..., str] | None = None,
|
|
475
|
-
input: SignatureType = None, # Input might be implicit or none
|
|
476
475
|
output: SignatureType = None, # Input might be implicit or none
|
|
477
476
|
tools: list[Callable[..., Any] | Any] | None = None,
|
|
478
477
|
servers: list[str | FlockMCPServerBase] | None = None,
|
|
@@ -496,7 +495,7 @@ class FlockFactory:
|
|
|
496
495
|
name=name,
|
|
497
496
|
description=description,
|
|
498
497
|
model=model,
|
|
499
|
-
input=
|
|
498
|
+
input="trigger_time: str | Time of scheduled execution",
|
|
500
499
|
output=output,
|
|
501
500
|
tools=tools,
|
|
502
501
|
servers=servers,
|
|
@@ -37,6 +37,14 @@ class InterpreterError(ValueError):
|
|
|
37
37
|
|
|
38
38
|
pass
|
|
39
39
|
|
|
40
|
+
class BreakException(Exception):
|
|
41
|
+
"""Signal a 'break' from the simulated loop."""
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
class ContinueException(Exception):
|
|
45
|
+
"""Signal a 'continue' in the simulated loop."""
|
|
46
|
+
pass
|
|
47
|
+
|
|
40
48
|
|
|
41
49
|
class PythonInterpreter:
|
|
42
50
|
r"""A customized python interpreter to control the execution of
|
|
@@ -44,8 +52,6 @@ class PythonInterpreter:
|
|
|
44
52
|
functions given in action space and import white list. It also supports
|
|
45
53
|
fuzzy variable matching to receive uncertain input variable name.
|
|
46
54
|
|
|
47
|
-
[Documentation omitted for brevity]
|
|
48
|
-
|
|
49
55
|
Args:
|
|
50
56
|
action_space (Dict[str, Any]): A dictionary mapping action names to
|
|
51
57
|
their corresponding functions or objects.
|
|
@@ -76,6 +82,9 @@ class PythonInterpreter:
|
|
|
76
82
|
"enum",
|
|
77
83
|
"json",
|
|
78
84
|
"ast",
|
|
85
|
+
"numpy",
|
|
86
|
+
"sympy",
|
|
87
|
+
"pandas",
|
|
79
88
|
] # default imports
|
|
80
89
|
self.verbose = verbose
|
|
81
90
|
|
|
@@ -95,50 +104,57 @@ class PythonInterpreter:
|
|
|
95
104
|
|
|
96
105
|
[Documentation omitted for brevity]
|
|
97
106
|
"""
|
|
98
|
-
if state is not None:
|
|
99
|
-
self.state.update(state)
|
|
100
|
-
if fuzz_state is not None:
|
|
101
|
-
self.fuzz_state.update(fuzz_state)
|
|
102
|
-
|
|
103
107
|
try:
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
f"Syntax error in code at line {e.lineno}: {error_line}\nError: {e}"
|
|
109
|
-
)
|
|
108
|
+
if state is not None:
|
|
109
|
+
self.state.update(state)
|
|
110
|
+
if fuzz_state is not None:
|
|
111
|
+
self.fuzz_state.update(fuzz_state)
|
|
110
112
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
113
|
+
try:
|
|
114
|
+
expression = ast.parse(code)
|
|
115
|
+
except SyntaxError as e:
|
|
116
|
+
error_line = code.splitlines()[e.lineno - 1]
|
|
117
|
+
self.log(
|
|
118
|
+
f"[Interpreter] Syntax error in code at line {e.lineno}: {error_line}\nError: {e}")
|
|
119
|
+
return(
|
|
120
|
+
f"Syntax error in code at line {e.lineno}: {error_line}\nError: {e}"
|
|
121
|
+
)
|
|
114
122
|
|
|
115
|
-
|
|
116
|
-
# Log the AST node being executed (using unparse if available)
|
|
123
|
+
result = None
|
|
117
124
|
if self.verbose:
|
|
118
|
-
|
|
119
|
-
node_repr = ast.unparse(node)
|
|
120
|
-
except Exception:
|
|
121
|
-
node_repr = ast.dump(node)
|
|
122
|
-
self.log(f"[Interpreter] Executing node {idx}: {node_repr}")
|
|
125
|
+
self.log("[Interpreter] Starting code execution...")
|
|
123
126
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
except InterpreterError as e:
|
|
127
|
-
if not keep_state:
|
|
128
|
-
self.clear_state()
|
|
129
|
-
msg = f"Evaluation of the code stopped at node {idx}. See:\n{e}"
|
|
130
|
-
raise InterpreterError(msg)
|
|
131
|
-
if line_result is not None:
|
|
132
|
-
result = line_result
|
|
127
|
+
for idx, node in enumerate(expression.body):
|
|
128
|
+
# Log the AST node being executed (using unparse if available)
|
|
133
129
|
if self.verbose:
|
|
134
|
-
|
|
130
|
+
try:
|
|
131
|
+
node_repr = ast.unparse(node)
|
|
132
|
+
except Exception:
|
|
133
|
+
node_repr = ast.dump(node)
|
|
134
|
+
self.log(f"[Interpreter] Executing node {idx}: {node_repr}")
|
|
135
135
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
136
|
+
try:
|
|
137
|
+
line_result = self._execute_ast(node)
|
|
138
|
+
except InterpreterError as e:
|
|
139
|
+
if not keep_state:
|
|
140
|
+
self.clear_state()
|
|
141
|
+
msg = f"Evaluation of the code stopped at node {idx}. See:\n{e}"
|
|
142
|
+
return msg
|
|
143
|
+
if line_result is not None:
|
|
144
|
+
result = line_result
|
|
145
|
+
if self.verbose:
|
|
146
|
+
self.log(f"[Interpreter] Node {idx} result: {result}")
|
|
140
147
|
|
|
141
|
-
|
|
148
|
+
if self.verbose:
|
|
149
|
+
self.log("[Interpreter] Finished code execution.")
|
|
150
|
+
if not keep_state:
|
|
151
|
+
self.clear_state()
|
|
152
|
+
|
|
153
|
+
return result
|
|
154
|
+
except Exception as e:
|
|
155
|
+
self.log(
|
|
156
|
+
f"[Interpreter] Error during code execution: {e}")
|
|
157
|
+
return f"[Interpreter] Error during code execution: {e}"
|
|
142
158
|
|
|
143
159
|
def clear_state(self) -> None:
|
|
144
160
|
r"""Initialize :obj:`state` and :obj:`fuzz_state`"""
|
|
@@ -236,7 +252,7 @@ class PythonInterpreter:
|
|
|
236
252
|
elif isinstance(expression, ast.Assert):
|
|
237
253
|
return self._execute_assert(expression)
|
|
238
254
|
else:
|
|
239
|
-
|
|
255
|
+
return(
|
|
240
256
|
f"{expression.__class__.__name__} is not supported."
|
|
241
257
|
)
|
|
242
258
|
|
|
@@ -253,17 +269,17 @@ class PythonInterpreter:
|
|
|
253
269
|
self.state[target.id] = value
|
|
254
270
|
elif isinstance(target, ast.Tuple):
|
|
255
271
|
if not isinstance(value, tuple):
|
|
256
|
-
|
|
272
|
+
return(
|
|
257
273
|
f"Expected type tuple, but got {value.__class__.__name__} instead."
|
|
258
274
|
)
|
|
259
275
|
if len(target.elts) != len(value):
|
|
260
|
-
|
|
276
|
+
return(
|
|
261
277
|
f"Expected {len(target.elts)} values but got {len(value)}."
|
|
262
278
|
)
|
|
263
279
|
for t, v in zip(target.elts, value):
|
|
264
280
|
self.state[self._execute_ast(t)] = v
|
|
265
281
|
else:
|
|
266
|
-
|
|
282
|
+
return(
|
|
267
283
|
f"Unsupported variable type. Expected ast.Name or ast.Tuple, got {target.__class__.__name__} instead."
|
|
268
284
|
)
|
|
269
285
|
|
|
@@ -297,7 +313,7 @@ class PythonInterpreter:
|
|
|
297
313
|
isinstance(current_value, (int, float))
|
|
298
314
|
and isinstance(increment_value, (int, float))
|
|
299
315
|
):
|
|
300
|
-
|
|
316
|
+
return(
|
|
301
317
|
f"Invalid types for augmented assignment: {type(current_value)}, {type(increment_value)}"
|
|
302
318
|
)
|
|
303
319
|
if isinstance(augassign.op, ast.Add):
|
|
@@ -309,7 +325,7 @@ class PythonInterpreter:
|
|
|
309
325
|
elif isinstance(augassign.op, ast.Div):
|
|
310
326
|
new_value = current_value / increment_value
|
|
311
327
|
else:
|
|
312
|
-
|
|
328
|
+
return(
|
|
313
329
|
f"Augmented assignment operator {augassign.op} is not supported"
|
|
314
330
|
)
|
|
315
331
|
self._assign(augassign.target, new_value)
|
|
@@ -319,7 +335,7 @@ class PythonInterpreter:
|
|
|
319
335
|
index = self._execute_ast(subscript.slice)
|
|
320
336
|
value = self._execute_ast(subscript.value)
|
|
321
337
|
if not isinstance(subscript.ctx, ast.Load):
|
|
322
|
-
|
|
338
|
+
return(
|
|
323
339
|
f"{subscript.ctx.__class__.__name__} is not supported for subscript."
|
|
324
340
|
)
|
|
325
341
|
if isinstance(value, (list, tuple)):
|
|
@@ -330,7 +346,7 @@ class PythonInterpreter:
|
|
|
330
346
|
close_matches = difflib.get_close_matches(index, list(value.keys()))
|
|
331
347
|
if len(close_matches) > 0:
|
|
332
348
|
return value[close_matches[0]]
|
|
333
|
-
|
|
349
|
+
return(f"Could not index {value} with '{index}'.")
|
|
334
350
|
|
|
335
351
|
def _execute_name(self, name: ast.Name):
|
|
336
352
|
if name.id in dir(builtins):
|
|
@@ -340,7 +356,7 @@ class PythonInterpreter:
|
|
|
340
356
|
elif isinstance(name.ctx, ast.Load):
|
|
341
357
|
return self._get_value_from_state(name.id)
|
|
342
358
|
else:
|
|
343
|
-
|
|
359
|
+
return(f"{name.ctx} is not supported.")
|
|
344
360
|
|
|
345
361
|
def _execute_condition(self, condition):
|
|
346
362
|
if isinstance(condition, ast.BoolOp):
|
|
@@ -355,12 +371,12 @@ class PythonInterpreter:
|
|
|
355
371
|
]
|
|
356
372
|
return any(results)
|
|
357
373
|
else:
|
|
358
|
-
|
|
374
|
+
return(
|
|
359
375
|
f"Boolean operator {condition.op} is not supported"
|
|
360
376
|
)
|
|
361
377
|
elif isinstance(condition, ast.Compare):
|
|
362
378
|
if len(condition.ops) > 1:
|
|
363
|
-
|
|
379
|
+
return(
|
|
364
380
|
"Cannot evaluate conditions with multiple operators"
|
|
365
381
|
)
|
|
366
382
|
left = self._execute_ast(condition.left)
|
|
@@ -387,7 +403,7 @@ class PythonInterpreter:
|
|
|
387
403
|
elif isinstance(comparator, ast.NotIn):
|
|
388
404
|
return left not in right
|
|
389
405
|
else:
|
|
390
|
-
|
|
406
|
+
return("Unsupported comparison operator")
|
|
391
407
|
elif isinstance(condition, ast.UnaryOp):
|
|
392
408
|
return self._execute_unaryop(condition)
|
|
393
409
|
elif isinstance(condition, ast.Name) or isinstance(condition, ast.Call):
|
|
@@ -395,7 +411,7 @@ class PythonInterpreter:
|
|
|
395
411
|
elif isinstance(condition, ast.Constant):
|
|
396
412
|
return bool(condition.value)
|
|
397
413
|
else:
|
|
398
|
-
|
|
414
|
+
return(
|
|
399
415
|
f"Unsupported condition type: {type(condition).__name__}"
|
|
400
416
|
)
|
|
401
417
|
|
|
@@ -428,7 +444,7 @@ class PythonInterpreter:
|
|
|
428
444
|
|
|
429
445
|
def _execute_import_from(self, import_from: ast.ImportFrom):
|
|
430
446
|
if import_from.module is None:
|
|
431
|
-
|
|
447
|
+
return('"from . import" is not supported.')
|
|
432
448
|
for import_name in import_from.names:
|
|
433
449
|
full_name = import_from.module + f".{import_name.name}"
|
|
434
450
|
self._validate_import(full_name)
|
|
@@ -440,12 +456,6 @@ class PythonInterpreter:
|
|
|
440
456
|
# We keep both as provided, but you may wish to consolidate these in your code.
|
|
441
457
|
|
|
442
458
|
def _execute_for(self, for_statement: ast.For):
|
|
443
|
-
class BreakException(Exception):
|
|
444
|
-
pass
|
|
445
|
-
|
|
446
|
-
class ContinueException(Exception):
|
|
447
|
-
pass
|
|
448
|
-
|
|
449
459
|
result = None
|
|
450
460
|
try:
|
|
451
461
|
for value in self._execute_ast(for_statement.iter):
|
|
@@ -462,12 +472,6 @@ class PythonInterpreter:
|
|
|
462
472
|
return result
|
|
463
473
|
|
|
464
474
|
def _execute_while(self, while_statement: ast.While):
|
|
465
|
-
class BreakException(Exception):
|
|
466
|
-
pass
|
|
467
|
-
|
|
468
|
-
class ContinueException(Exception):
|
|
469
|
-
pass
|
|
470
|
-
|
|
471
475
|
result = None
|
|
472
476
|
try:
|
|
473
477
|
while self._execute_condition(while_statement.test):
|
|
@@ -540,7 +544,7 @@ class PythonInterpreter:
|
|
|
540
544
|
found_name = True
|
|
541
545
|
return
|
|
542
546
|
if not found_name:
|
|
543
|
-
|
|
547
|
+
return(
|
|
544
548
|
f"It is not permitted to import modules "
|
|
545
549
|
f"than module white list (try to import {full_name})."
|
|
546
550
|
)
|
|
@@ -577,7 +581,7 @@ class PythonInterpreter:
|
|
|
577
581
|
elif isinstance(operator, ast.MatMult):
|
|
578
582
|
return left @ right
|
|
579
583
|
else:
|
|
580
|
-
|
|
584
|
+
return(f"Operator not supported: {operator}")
|
|
581
585
|
|
|
582
586
|
def _execute_unaryop(self, unaryop: ast.UnaryOp):
|
|
583
587
|
operand = self._execute_ast(unaryop.operand)
|
|
@@ -592,31 +596,33 @@ class PythonInterpreter:
|
|
|
592
596
|
elif isinstance(operator, ast.Invert):
|
|
593
597
|
return ~operand
|
|
594
598
|
else:
|
|
595
|
-
|
|
599
|
+
return(f"Operator not supported: {operator}")
|
|
596
600
|
|
|
597
601
|
def _execute_listcomp(self, comp: ast.ListComp):
|
|
598
|
-
return
|
|
599
|
-
|
|
600
|
-
def _execute_dictcomp(self, comp: ast.DictComp):
|
|
601
|
-
return {
|
|
602
|
-
self._execute_comp(comp.key, comp.generators): self._execute_comp(
|
|
603
|
-
comp.value, comp.generators
|
|
604
|
-
)
|
|
605
|
-
}
|
|
602
|
+
return self._execute_comp(comp.elt, comp.generators)
|
|
606
603
|
|
|
607
604
|
def _execute_setcomp(self, comp: ast.SetComp):
|
|
608
|
-
return
|
|
605
|
+
return set(self._execute_comp(comp.elt, comp.generators))
|
|
606
|
+
|
|
607
|
+
def _execute_dictcomp(self, comp: ast.DictComp):
|
|
608
|
+
keys = self._execute_comp(comp.key, comp.generators)
|
|
609
|
+
values = self._execute_comp(comp.value, comp.generators)
|
|
610
|
+
return dict(zip(keys, values))
|
|
609
611
|
|
|
610
612
|
def _execute_comp(self, elt, generators):
|
|
613
|
+
# Base-case: wrap the single element in a list so that
|
|
614
|
+
# callers can safely .extend() it.
|
|
611
615
|
if not generators:
|
|
612
|
-
return self._execute_ast(elt)
|
|
616
|
+
return [self._execute_ast(elt)]
|
|
617
|
+
|
|
613
618
|
gen = generators[0]
|
|
614
|
-
|
|
619
|
+
acc: list[Any] = []
|
|
615
620
|
for value in self._execute_ast(gen.iter):
|
|
616
621
|
self._assign(gen.target, value)
|
|
617
622
|
if all(self._execute_condition(if_cond) for if_cond in gen.ifs):
|
|
618
|
-
|
|
619
|
-
return
|
|
623
|
+
acc.extend(self._execute_comp(elt, generators[1:]))
|
|
624
|
+
return acc
|
|
625
|
+
|
|
620
626
|
|
|
621
627
|
def _execute_generatorexp(self, genexp: ast.GeneratorExp):
|
|
622
628
|
def generator():
|
|
@@ -631,7 +637,7 @@ class PythonInterpreter:
|
|
|
631
637
|
elif key in self.fuzz_state:
|
|
632
638
|
return self.fuzz_state[key]
|
|
633
639
|
else:
|
|
634
|
-
|
|
640
|
+
return(f"The variable `{key}` is not defined.")
|
|
635
641
|
|
|
636
642
|
|
|
637
643
|
class TextPrompt(str):
|
|
@@ -9,7 +9,7 @@ from typing import Any, Literal
|
|
|
9
9
|
from dspy import Tool
|
|
10
10
|
|
|
11
11
|
from flock.core.logging.logging import get_logger
|
|
12
|
-
from flock.core.util.
|
|
12
|
+
from flock.core.util.splitter import split_top_level
|
|
13
13
|
|
|
14
14
|
# Import split_top_level (assuming it's moved or copied appropriately)
|
|
15
15
|
# Option 1: If moved to a shared util
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Utility functions for resolving input keys to their corresponding values."""
|
|
2
2
|
|
|
3
3
|
from flock.core.context.context import FlockContext
|
|
4
|
-
from flock.core.util.
|
|
4
|
+
from flock.core.util.splitter import split_top_level
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def get_callable_members(obj):
|
flock/tools/code_tools.py
CHANGED
|
@@ -31,6 +31,30 @@ def code_evaluate_math(expression: str) -> float:
|
|
|
31
31
|
|
|
32
32
|
@traced_and_logged
|
|
33
33
|
def code_code_eval(python_code: str) -> str:
|
|
34
|
+
"""A Python code evaluation tool that executes Python code and returns the result.
|
|
35
|
+
|
|
36
|
+
The code may not be markdown-escaped with triple backticks.
|
|
37
|
+
It is expected to be a valid Python code snippet that can be executed directly.
|
|
38
|
+
The code is executed in a controlled environment with a limited set of libraries.
|
|
39
|
+
It allows the use of the following libraries:
|
|
40
|
+
"os",
|
|
41
|
+
"math",
|
|
42
|
+
"random",
|
|
43
|
+
"datetime",
|
|
44
|
+
"time",
|
|
45
|
+
"string",
|
|
46
|
+
"collections",
|
|
47
|
+
"itertools",
|
|
48
|
+
"functools",
|
|
49
|
+
"typing",
|
|
50
|
+
"enum",
|
|
51
|
+
"json",
|
|
52
|
+
"ast",
|
|
53
|
+
"numpy",
|
|
54
|
+
"sympy",
|
|
55
|
+
"pandas",
|
|
56
|
+
"httpx",
|
|
57
|
+
"""
|
|
34
58
|
try:
|
|
35
59
|
result = PythonInterpreter(
|
|
36
60
|
{},
|
|
@@ -48,9 +72,96 @@ def code_code_eval(python_code: str) -> str:
|
|
|
48
72
|
"enum",
|
|
49
73
|
"json",
|
|
50
74
|
"ast",
|
|
75
|
+
"numpy",
|
|
76
|
+
"sympy",
|
|
77
|
+
"pandas",
|
|
51
78
|
],
|
|
52
79
|
verbose=True,
|
|
53
80
|
).execute(python_code)
|
|
54
81
|
return result
|
|
55
82
|
except Exception:
|
|
56
83
|
raise
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@traced_and_logged
|
|
87
|
+
def docker_code_execute(python_code: str) -> str:
|
|
88
|
+
"""Execute Python code in a sandboxed Docker container."""
|
|
89
|
+
import ast
|
|
90
|
+
import os
|
|
91
|
+
import pathlib
|
|
92
|
+
import platform
|
|
93
|
+
import shutil
|
|
94
|
+
import textwrap
|
|
95
|
+
import uuid
|
|
96
|
+
|
|
97
|
+
import docker
|
|
98
|
+
def _auto_print_last_expr(code: str) -> str:
|
|
99
|
+
"""If the last top-level statement is a bare expression,
|
|
100
|
+
append a print() so script mode surfaces its value.
|
|
101
|
+
"""
|
|
102
|
+
tree = ast.parse(code, mode="exec")
|
|
103
|
+
if tree.body and isinstance(tree.body[-1], ast.Expr):
|
|
104
|
+
# Re-extract the exact source of that expression
|
|
105
|
+
expr_src = textwrap.dedent(
|
|
106
|
+
code.splitlines()[tree.body[-1].lineno - 1]
|
|
107
|
+
)
|
|
108
|
+
code += f"\nprint({expr_src})"
|
|
109
|
+
return code
|
|
110
|
+
# --- 1. Figure out a base directory that exists on this OS ----------
|
|
111
|
+
if platform.system() == "Windows":
|
|
112
|
+
base_dir = pathlib.Path(os.getenv("SANDBOX_BASE_DIR", r"C:\sandboxes"))
|
|
113
|
+
else: # Linux, macOS, WSL2
|
|
114
|
+
base_dir = pathlib.Path(os.getenv("SANDBOX_BASE_DIR", "/var/sandboxes"))
|
|
115
|
+
|
|
116
|
+
base_dir.mkdir(parents=True, exist_ok=True)
|
|
117
|
+
|
|
118
|
+
sandbox_id = f"sbox-{uuid.uuid4()}"
|
|
119
|
+
workdir = base_dir / sandbox_id
|
|
120
|
+
workdir.mkdir(parents=True, exist_ok=False)
|
|
121
|
+
|
|
122
|
+
# Docker’s HTTP API always wants POSIX‐style paths (“/”, drive letter allowed).
|
|
123
|
+
host_path = workdir.resolve().as_posix() # e.g. "C:/sandboxes/…"
|
|
124
|
+
|
|
125
|
+
client = docker.from_env()
|
|
126
|
+
image = "python:3.12-slim"
|
|
127
|
+
|
|
128
|
+
# --- 2. Decide whether we can / should request the gVisor runtime ---
|
|
129
|
+
runtime_args = {}
|
|
130
|
+
if platform.system() != "Windows" and shutil.which("runsc"):
|
|
131
|
+
runtime_args["runtime"] = "runsc" # gVisor on Linux & macOS
|
|
132
|
+
|
|
133
|
+
container = client.containers.run(
|
|
134
|
+
image,
|
|
135
|
+
name=sandbox_id,
|
|
136
|
+
command=["sleep", "infinity"],
|
|
137
|
+
user="65534:65534", # nobody
|
|
138
|
+
network_mode="none",
|
|
139
|
+
volumes={host_path: {"bind": "/workspace", "mode": "rw"}},
|
|
140
|
+
mem_limit="4g",
|
|
141
|
+
cpu_period=100_000,
|
|
142
|
+
cpu_quota=200_000, # 2 vCPU
|
|
143
|
+
security_opt=["no-new-privileges"],
|
|
144
|
+
detach=True,
|
|
145
|
+
**runtime_args,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
try:
|
|
149
|
+
def exec_code(cmd: list[str], timeout: int = 30) -> str:
|
|
150
|
+
exec_id = client.api.exec_create(
|
|
151
|
+
container.id, cmd, workdir="/workspace"
|
|
152
|
+
)["Id"]
|
|
153
|
+
return client.api.exec_start(
|
|
154
|
+
exec_id, stream=False, demux=False, tty=False,
|
|
155
|
+
).decode()
|
|
156
|
+
|
|
157
|
+
# --- 3. Copy code in and execute --------------------------------
|
|
158
|
+
(workdir / "main.py").write_text(_auto_print_last_expr(python_code), encoding="utf-8")
|
|
159
|
+
stdout = exec_code(["python", "main.py"], timeout=30)
|
|
160
|
+
return stdout.strip()
|
|
161
|
+
|
|
162
|
+
finally:
|
|
163
|
+
# --- 4. Tear everything down ------------------------------------
|
|
164
|
+
container.remove(force=True)
|
|
165
|
+
shutil.rmtree(workdir, ignore_errors=True)
|
|
166
|
+
|
|
167
|
+
|
flock/tools/file_tools.py
CHANGED
|
@@ -23,10 +23,19 @@ def file_get_anything_as_markdown(url_or_file_path: str):
|
|
|
23
23
|
)
|
|
24
24
|
|
|
25
25
|
|
|
26
|
+
@traced_and_logged
|
|
27
|
+
def file_append_to_file(content: str, filename: str):
|
|
28
|
+
try:
|
|
29
|
+
with open(filename, "a", encoding="utf-8") as f:
|
|
30
|
+
f.write(content)
|
|
31
|
+
except Exception:
|
|
32
|
+
raise
|
|
33
|
+
|
|
34
|
+
|
|
26
35
|
@traced_and_logged
|
|
27
36
|
def file_save_to_file(content: str, filename: str):
|
|
28
37
|
try:
|
|
29
|
-
with open(filename, "w") as f:
|
|
38
|
+
with open(filename, "w", encoding="utf-8") as f:
|
|
30
39
|
f.write(content)
|
|
31
40
|
except Exception:
|
|
32
41
|
raise
|
|
@@ -20,7 +20,7 @@ if TYPE_CHECKING:
|
|
|
20
20
|
from flock.core.logging.logging import (
|
|
21
21
|
get_logger as get_flock_logger, # For logging within the new endpoint
|
|
22
22
|
)
|
|
23
|
-
from flock.core.util.
|
|
23
|
+
from flock.core.util.splitter import parse_schema
|
|
24
24
|
|
|
25
25
|
# Import the dependency to get the current Flock instance
|
|
26
26
|
from flock.webapp.app.dependencies import (
|
flock/webapp/app/main.py
CHANGED
|
@@ -34,7 +34,7 @@ from flock.core.api.run_store import RunStore
|
|
|
34
34
|
from flock.core.flock import Flock # For type hinting
|
|
35
35
|
from flock.core.flock_scheduler import FlockScheduler
|
|
36
36
|
from flock.core.logging.logging import get_logger # For logging
|
|
37
|
-
from flock.core.util.
|
|
37
|
+
from flock.core.util.splitter import parse_schema
|
|
38
38
|
|
|
39
39
|
# Import UI-specific routers
|
|
40
40
|
from flock.webapp.app.api import (
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: flock-core
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.515
|
|
4
4
|
Summary: Declarative LLM Orchestration at Scale
|
|
5
5
|
Author-email: Andre Ratzenberger <andre.ratzenberger@whiteduck.de>
|
|
6
6
|
License-File: LICENSE
|
|
@@ -69,6 +69,7 @@ Provides-Extra: all-tools
|
|
|
69
69
|
Requires-Dist: azure-identity>=1.23.0; extra == 'all-tools'
|
|
70
70
|
Requires-Dist: azure-search-documents>=11.5.2; extra == 'all-tools'
|
|
71
71
|
Requires-Dist: azure-storage-blob>=12.25.1; extra == 'all-tools'
|
|
72
|
+
Requires-Dist: docker>=7.1.0; extra == 'all-tools'
|
|
72
73
|
Requires-Dist: docling>=2.18.0; extra == 'all-tools'
|
|
73
74
|
Requires-Dist: duckduckgo-search>=7.3.2; extra == 'all-tools'
|
|
74
75
|
Requires-Dist: markdownify>=0.14.1; extra == 'all-tools'
|
|
@@ -83,6 +84,8 @@ Requires-Dist: docling>=2.18.0; extra == 'basic-tools'
|
|
|
83
84
|
Requires-Dist: duckduckgo-search>=7.3.2; extra == 'basic-tools'
|
|
84
85
|
Requires-Dist: markdownify>=0.14.1; extra == 'basic-tools'
|
|
85
86
|
Requires-Dist: tavily-python>=0.5.0; extra == 'basic-tools'
|
|
87
|
+
Provides-Extra: code-tools
|
|
88
|
+
Requires-Dist: docker>=7.1.0; extra == 'code-tools'
|
|
86
89
|
Provides-Extra: evaluation
|
|
87
90
|
Requires-Dist: datasets>=3.2.0; extra == 'evaluation'
|
|
88
91
|
Requires-Dist: rouge-score>=0.1.2; extra == 'evaluation'
|
|
@@ -29,7 +29,7 @@ flock/core/__init__.py,sha256=juwyNr3QqKXUS5-E3hlMYRhgqHgQBqgtP12OF3tUCAI,1249
|
|
|
29
29
|
flock/core/flock.py,sha256=iR4i0_z0w2ns_iHbP7FqN--7wlsPIWch1H-BVecPs_I,38205
|
|
30
30
|
flock/core/flock_agent.py,sha256=Hl6TONSiJi2I-N_49-1hkW2q_hyPXMebMr-5oZLI-PY,48842
|
|
31
31
|
flock/core/flock_evaluator.py,sha256=TPy6u6XX3cqkY1r9NW1w2lTwCMNW7pxhFYKLefnEbXg,1820
|
|
32
|
-
flock/core/flock_factory.py,sha256=
|
|
32
|
+
flock/core/flock_factory.py,sha256=siCShld0mHPzjsw2_TUhg_nesMYhocJympt1M8ch6p8,18724
|
|
33
33
|
flock/core/flock_module.py,sha256=ObILimpVaPnaaqYvcBYJJ20lQzfrjgTdADplaNRjHU0,7448
|
|
34
34
|
flock/core/flock_registry.py,sha256=KzdFfc3QC-Dk42G24hdf6Prp3HvGj9ymXR3TTBe-T-A,27161
|
|
35
35
|
flock/core/flock_router.py,sha256=1OAXDsdaIIFApEfo6SRfFEDoTuGt3Si7n2MXiySEfis,2644
|
|
@@ -52,8 +52,9 @@ flock/core/evaluation/utils.py,sha256=S5M0uTFcClphZsR5EylEzrRNK-1434yImiGYL4pR_5
|
|
|
52
52
|
flock/core/execution/batch_executor.py,sha256=mHwCI-DHqApCv_EVCN0ZOUd-LCQLjREpxKbAUPC0pcY,15266
|
|
53
53
|
flock/core/execution/evaluation_executor.py,sha256=D9EO0sU-2qWj3vomjmUUi-DOtHNJNFRf30kGDHuzREE,17702
|
|
54
54
|
flock/core/execution/local_executor.py,sha256=rnIQvaJOs6zZORUcR3vvyS6LPREDJTjaygl_Db0M8ao,952
|
|
55
|
+
flock/core/execution/opik_executor.py,sha256=tl2Ti9NM_9WtcjXvJ0c7up-syRNq_OsLmiuYWqkGV4k,3325
|
|
55
56
|
flock/core/execution/temporal_executor.py,sha256=dHcb0xuzPFWU_wbwTgI7glLNyyppei93Txs2sapjhaw,6283
|
|
56
|
-
flock/core/interpreter/python_interpreter.py,sha256=
|
|
57
|
+
flock/core/interpreter/python_interpreter.py,sha256=4-wRsxC6-gToEdRr_pp-n2idWwe_Y2zN0o3TbzUPhy0,26632
|
|
57
58
|
flock/core/logging/__init__.py,sha256=xn5fC-8IgsdIv0ywe_cICK1KVhTrVD8t-jYORg0ETUA,155
|
|
58
59
|
flock/core/logging/logging.py,sha256=VyBsin-3q8UQ0DY-K72t8FtrGJQbUwIfzxaC7rIWMvQ,19820
|
|
59
60
|
flock/core/logging/telemetry.py,sha256=Trssqx02SBovTL843YwY3L-ZGj3KvcfMHLMU7Syk8L0,6561
|
|
@@ -79,7 +80,7 @@ flock/core/mcp/types/handlers.py,sha256=VEpOx5ShvlvOEvgo2Fs5rql-x0obVQYgEpcb8FBr
|
|
|
79
80
|
flock/core/mcp/types/types.py,sha256=2amE-oastGe1GGVI4gbH2ltCX7QvYnJebSArATvttUU,11410
|
|
80
81
|
flock/core/mcp/util/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
81
82
|
flock/core/mcp/util/helpers.py,sha256=Xlf4iKW_lZxsVMTRoOnV29JsJfAppfmEJrb6sIcoCH4,636
|
|
82
|
-
flock/core/mixin/dspy_integration.py,sha256=
|
|
83
|
+
flock/core/mixin/dspy_integration.py,sha256=rsZ9bkCKlT3jJRncnwwngTtffKcF2WloFvSleaZ0QRk,17746
|
|
83
84
|
flock/core/mixin/prompt_parser.py,sha256=eOqI-FK3y17gVqpc_y5GF-WmK1Jv8mFlkZxTcgweoxI,5121
|
|
84
85
|
flock/core/serialization/__init__.py,sha256=CML7fPgG6p4c0CDBlJ_uwV1aZZhJKK9uy3IoIHfO87w,431
|
|
85
86
|
flock/core/serialization/callable_registry.py,sha256=sUZECTZWsM3fJ8FDRQ-FgLNW9hF26nY17AD6fJKADMc,1419
|
|
@@ -91,9 +92,9 @@ flock/core/serialization/serialization_utils.py,sha256=AHRf90trgnj2Q6aaGaq5eja5P
|
|
|
91
92
|
flock/core/util/cli_helper.py,sha256=9MiAw8y0IRlWKF7lRYViRFzSwbWSeiiLv0usyhn8XlU,49966
|
|
92
93
|
flock/core/util/file_path_utils.py,sha256=Odf7uU32C-x1KNighbNERSiMtkzW4h8laABIoFK7A5M,6246
|
|
93
94
|
flock/core/util/hydrator.py,sha256=ARg4ufXNlfAESDaxPeU8j6TOJ2ywzfl00KAIfVHGIxo,10699
|
|
94
|
-
flock/core/util/input_resolver.py,sha256=
|
|
95
|
+
flock/core/util/input_resolver.py,sha256=QE3EjP7xTRNHuH6o77O3YhlhiCWOcnDg8cnSXzngKnM,4704
|
|
95
96
|
flock/core/util/loader.py,sha256=j3q2qem5bFMP2SmMuYjb-ISxsNGNZd1baQmpvAnRUUk,2244
|
|
96
|
-
flock/core/util/
|
|
97
|
+
flock/core/util/splitter.py,sha256=rDLnZX158PWkmW8vi2UfMLAMRXcHQFUIydAABd-lDGw,7154
|
|
97
98
|
flock/evaluators/__init__.py,sha256=Y0cEkx0dujRmy--TDpKoTqFSLzbyFz8BwEOv8kdSUhg,22
|
|
98
99
|
flock/evaluators/declarative/__init__.py,sha256=Y0cEkx0dujRmy--TDpKoTqFSLzbyFz8BwEOv8kdSUhg,22
|
|
99
100
|
flock/evaluators/declarative/declarative_evaluator.py,sha256=tulTpUOXhF-wMe5a9ULpsCiS1o2Z-DOXOUTqOSzVYqI,7397
|
|
@@ -480,8 +481,8 @@ flock/themes/zenwritten-dark.toml,sha256=To5l6520_3UqAGiEumpzGWsHhXxqu9ThrMildXK
|
|
|
480
481
|
flock/themes/zenwritten-light.toml,sha256=G1iEheCPfBNsMTGaVpEVpDzYBHA_T-MV27rolUYolmE,1666
|
|
481
482
|
flock/tools/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
482
483
|
flock/tools/azure_tools.py,sha256=OTJsb0B4l70GcD1W3ZMDHWd3X8nEnszhhz2sllD2z9E,30187
|
|
483
|
-
flock/tools/code_tools.py,sha256=
|
|
484
|
-
flock/tools/file_tools.py,sha256=
|
|
484
|
+
flock/tools/code_tools.py,sha256=xLpuFl84y_GVzmIBe4qrr7h9wI3yWpM-M21GgEUjSjE,5247
|
|
485
|
+
flock/tools/file_tools.py,sha256=VYjT942NqDMTnizHiF41O4Af6ySseSvahRNVVrGMXl8,4850
|
|
485
486
|
flock/tools/github_tools.py,sha256=HH47-4K3HL6tRJhZhUttWDo2aloP9Hs12wRC_f_-Vkc,5329
|
|
486
487
|
flock/tools/markdown_tools.py,sha256=94fjGAJ5DEutoioD0ke-YRbxF6IWJQKuPVBLkNqdBo4,6345
|
|
487
488
|
flock/tools/system_tools.py,sha256=IUB8MiSxtQH5ZfTGOck3vl4TKva8m1lfU4-W5D5b-4w,202
|
|
@@ -494,13 +495,13 @@ flock/webapp/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,
|
|
|
494
495
|
flock/webapp/app/chat.py,sha256=d5a_mr3H2nuWNFSpSlI_HyqX-J_4krndd4A-8S25EKM,28679
|
|
495
496
|
flock/webapp/app/config.py,sha256=lqmneujnNZk-EFJV5cWpvxkqisxH3T3zT_YOI0JYThE,4809
|
|
496
497
|
flock/webapp/app/dependencies.py,sha256=JUcwY1N6SZplU141lMN2wk9dOC9er5HCedrKTJN9wJk,5533
|
|
497
|
-
flock/webapp/app/main.py,sha256=
|
|
498
|
+
flock/webapp/app/main.py,sha256=54DHCA4jaOBrW3Lly0t-r_gSOEBj96G4Izt5pkbFZSw,57142
|
|
498
499
|
flock/webapp/app/models_ui.py,sha256=vrEBLbhEp6FziAgBSFOLT1M7ckwadsTdT7qus5_NduE,329
|
|
499
500
|
flock/webapp/app/theme_mapper.py,sha256=QzWwLWpED78oYp3FjZ9zxv1KxCyj43m8MZ0fhfzz37w,34302
|
|
500
501
|
flock/webapp/app/utils.py,sha256=RF8DMKKAj1XPmm4txUdo2OdswI1ATQ7cqUm6G9JFDzA,2942
|
|
501
502
|
flock/webapp/app/api/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
502
503
|
flock/webapp/app/api/agent_management.py,sha256=5xqO94QjjAYvxImyjKV9EGUQOvo4n3eqs7pGwGPSQJ4,10394
|
|
503
|
-
flock/webapp/app/api/execution.py,sha256=
|
|
504
|
+
flock/webapp/app/api/execution.py,sha256=wRuJ3v2PXgpyiPZ_0t4qYDG7tgQgBlAn1I9QZocQN2U,12995
|
|
504
505
|
flock/webapp/app/api/flock_management.py,sha256=1o-6-36kTnUjI3am_BqLpdrcz0aqFXrxE-hQHIFcCsg,4869
|
|
505
506
|
flock/webapp/app/api/registry_viewer.py,sha256=IoInxJiRR0yFlecG_l2_eRc6l35RQQyEDMG9BcBkipY,1020
|
|
506
507
|
flock/webapp/app/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -560,8 +561,8 @@ flock/workflow/agent_execution_activity.py,sha256=Gy6FtuVAjf0NiUXmC3syS2eJpNQF4R
|
|
|
560
561
|
flock/workflow/flock_workflow.py,sha256=iSUF_soFvWar0ffpkzE4irkDZRx0p4HnwmEBi_Ne2sY,9666
|
|
561
562
|
flock/workflow/temporal_config.py,sha256=3_8O7SDEjMsSMXsWJBfnb6XTp0TFaz39uyzSlMTSF_I,3988
|
|
562
563
|
flock/workflow/temporal_setup.py,sha256=YIHnSBntzOchHfMSh8hoLeNXrz3B1UbR14YrR6soM7A,1606
|
|
563
|
-
flock_core-0.4.
|
|
564
|
-
flock_core-0.4.
|
|
565
|
-
flock_core-0.4.
|
|
566
|
-
flock_core-0.4.
|
|
567
|
-
flock_core-0.4.
|
|
564
|
+
flock_core-0.4.515.dist-info/METADATA,sha256=7nfOA03KGxxfxZ5-sm_GuTHXqhhD1NRxo_hvp6d8zBI,22786
|
|
565
|
+
flock_core-0.4.515.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
566
|
+
flock_core-0.4.515.dist-info/entry_points.txt,sha256=rWaS5KSpkTmWySURGFZk6PhbJ87TmvcFQDi2uzjlagQ,37
|
|
567
|
+
flock_core-0.4.515.dist-info/licenses/LICENSE,sha256=iYEqWy0wjULzM9GAERaybP4LBiPeu7Z1NEliLUdJKSc,1072
|
|
568
|
+
flock_core-0.4.515.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|