fleet-python 0.2.66b4__py3-none-any.whl → 0.2.68__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 fleet-python might be problematic. Click here for more details.
- examples/export_tasks.py +11 -1
- examples/import_tasks.py +15 -0
- fleet/_async/client.py +36 -3
- fleet/_async/env/client.py +29 -3
- fleet/_async/models.py +3 -0
- fleet/_async/tasks.py +44 -26
- fleet/client.py +36 -3
- fleet/env/__init__.py +8 -0
- fleet/env/client.py +29 -3
- fleet/models.py +5 -0
- fleet/tasks.py +39 -25
- {fleet_python-0.2.66b4.dist-info → fleet_python-0.2.68.dist-info}/METADATA +1 -1
- {fleet_python-0.2.66b4.dist-info → fleet_python-0.2.68.dist-info}/RECORD +16 -18
- fleet/verifiers/parsing.py +0 -106
- tests/test_verifier_security.py +0 -427
- {fleet_python-0.2.66b4.dist-info → fleet_python-0.2.68.dist-info}/WHEEL +0 -0
- {fleet_python-0.2.66b4.dist-info → fleet_python-0.2.68.dist-info}/licenses/LICENSE +0 -0
- {fleet_python-0.2.66b4.dist-info → fleet_python-0.2.68.dist-info}/top_level.txt +0 -0
fleet/tasks.py
CHANGED
|
@@ -202,23 +202,25 @@ class Task(BaseModel):
|
|
|
202
202
|
)
|
|
203
203
|
self.verifier = verifier
|
|
204
204
|
|
|
205
|
-
def make_env(
|
|
205
|
+
def make_env(
|
|
206
|
+
self,
|
|
207
|
+
region: Optional[str] = None,
|
|
208
|
+
image_type: Optional[str] = None,
|
|
209
|
+
ttl_seconds: Optional[int] = None,
|
|
210
|
+
run_id: Optional[str] = None,
|
|
211
|
+
):
|
|
206
212
|
"""Create an environment instance for this task's environment.
|
|
207
213
|
|
|
208
|
-
Uses the task's env_id (and version if present) to create the env.
|
|
214
|
+
Alias for make() method. Uses the task's env_id (and version if present) to create the env.
|
|
209
215
|
"""
|
|
210
|
-
|
|
211
|
-
raise ValueError("Task has no env_id defined")
|
|
212
|
-
# Deferred import to avoid circular dependencies
|
|
213
|
-
from .client import Fleet
|
|
214
|
-
|
|
215
|
-
return Fleet().make(env_key=self.env_key, region=region)
|
|
216
|
+
return self.make(region=region, image_type=image_type, ttl_seconds=ttl_seconds, run_id=run_id)
|
|
216
217
|
|
|
217
218
|
def make(
|
|
218
219
|
self,
|
|
219
220
|
region: Optional[str] = None,
|
|
220
221
|
image_type: Optional[str] = None,
|
|
221
222
|
ttl_seconds: Optional[int] = None,
|
|
223
|
+
run_id: Optional[str] = None,
|
|
222
224
|
):
|
|
223
225
|
"""Create an environment instance with task's configuration.
|
|
224
226
|
|
|
@@ -226,11 +228,13 @@ class Task(BaseModel):
|
|
|
226
228
|
- env_key (env_id + version)
|
|
227
229
|
- data_key (data_id + data_version, if present)
|
|
228
230
|
- env_variables (if present)
|
|
231
|
+
- run_id (if present)
|
|
229
232
|
|
|
230
233
|
Args:
|
|
231
234
|
region: Optional AWS region for the environment
|
|
232
235
|
image_type: Optional image type for the environment
|
|
233
236
|
ttl_seconds: Optional TTL in seconds for the instance
|
|
237
|
+
run_id: Optional run ID to group instances
|
|
234
238
|
|
|
235
239
|
Returns:
|
|
236
240
|
Environment instance configured for this task
|
|
@@ -238,7 +242,7 @@ class Task(BaseModel):
|
|
|
238
242
|
Example:
|
|
239
243
|
task = fleet.Task(key="my-task", prompt="...", env_id="my-env",
|
|
240
244
|
data_id="my-data", data_version="v1.0")
|
|
241
|
-
env = task.make(region="us-west-2")
|
|
245
|
+
env = task.make(region="us-west-2", run_id="my-batch-123")
|
|
242
246
|
"""
|
|
243
247
|
if not self.env_id:
|
|
244
248
|
raise ValueError("Task has no env_id defined")
|
|
@@ -253,6 +257,7 @@ class Task(BaseModel):
|
|
|
253
257
|
env_variables=self.env_variables if self.env_variables else None,
|
|
254
258
|
image_type=image_type,
|
|
255
259
|
ttl_seconds=ttl_seconds,
|
|
260
|
+
run_id=run_id,
|
|
256
261
|
)
|
|
257
262
|
|
|
258
263
|
|
|
@@ -272,14 +277,17 @@ def verifier_from_string(
|
|
|
272
277
|
"""
|
|
273
278
|
try:
|
|
274
279
|
import inspect
|
|
280
|
+
import re
|
|
275
281
|
from .verifiers import SyncVerifierFunction
|
|
276
282
|
from .verifiers.code import TASK_SUCCESSFUL_SCORE, TASK_FAILED_SCORE
|
|
277
283
|
from .verifiers.db import IgnoreConfig
|
|
278
|
-
from .verifiers.parsing import parse_and_validate_verifier
|
|
279
284
|
|
|
280
|
-
#
|
|
281
|
-
#
|
|
282
|
-
|
|
285
|
+
# Strip @verifier decorator if present to avoid double-wrapping
|
|
286
|
+
# Remove lines like: @verifier(key="...")
|
|
287
|
+
cleaned_code = re.sub(r"@verifier\([^)]*\)\s*\n", "", verifier_func)
|
|
288
|
+
# Also remove the verifier import if present
|
|
289
|
+
cleaned_code = re.sub(r"from fleet import.*verifier.*\n", "", cleaned_code)
|
|
290
|
+
cleaned_code = re.sub(r"import.*verifier.*\n", "", cleaned_code)
|
|
283
291
|
|
|
284
292
|
# Create a globals namespace with all required imports
|
|
285
293
|
exec_globals = globals().copy()
|
|
@@ -295,18 +303,20 @@ def verifier_from_string(
|
|
|
295
303
|
# Create a local namespace for executing the code
|
|
296
304
|
local_namespace = {}
|
|
297
305
|
|
|
298
|
-
# Execute the verifier code in the namespace
|
|
299
|
-
|
|
300
|
-
exec(verifier_func, exec_globals, local_namespace)
|
|
306
|
+
# Execute the cleaned verifier code in the namespace
|
|
307
|
+
exec(cleaned_code, exec_globals, local_namespace)
|
|
301
308
|
|
|
302
|
-
#
|
|
303
|
-
|
|
309
|
+
# Find the function that was defined (not imported)
|
|
310
|
+
# Functions defined via exec have co_filename == '<string>'
|
|
311
|
+
# Imported functions have their actual module file path
|
|
312
|
+
func_obj = None
|
|
313
|
+
for name, obj in local_namespace.items():
|
|
314
|
+
if inspect.isfunction(obj) and obj.__code__.co_filename == "<string>":
|
|
315
|
+
func_obj = obj
|
|
316
|
+
break
|
|
304
317
|
|
|
305
318
|
if func_obj is None:
|
|
306
|
-
raise ValueError(
|
|
307
|
-
|
|
308
|
-
if not inspect.isfunction(func_obj):
|
|
309
|
-
raise ValueError(f"'{func_name}' is not a function")
|
|
319
|
+
raise ValueError("No function found in verifier code")
|
|
310
320
|
|
|
311
321
|
# Create an SyncVerifierFunction instance with raw code
|
|
312
322
|
verifier_instance = SyncVerifierFunction(
|
|
@@ -384,7 +394,7 @@ def load_tasks(
|
|
|
384
394
|
|
|
385
395
|
|
|
386
396
|
def update_task(
|
|
387
|
-
task_key: str, prompt: Optional[str] = None, verifier_code: Optional[str] = None
|
|
397
|
+
task_key: str, prompt: Optional[str] = None, verifier_code: Optional[str] = None, metadata: Optional[Dict[str, Any]] = None
|
|
388
398
|
):
|
|
389
399
|
"""Convenience function to update an existing task.
|
|
390
400
|
|
|
@@ -392,6 +402,7 @@ def update_task(
|
|
|
392
402
|
task_key: The key of the task to update
|
|
393
403
|
prompt: New prompt text for the task (optional)
|
|
394
404
|
verifier_code: Python code for task verification (optional)
|
|
405
|
+
metadata: Additional metadata for the task (optional)
|
|
395
406
|
|
|
396
407
|
Returns:
|
|
397
408
|
TaskResponse containing the updated task details
|
|
@@ -399,16 +410,19 @@ def update_task(
|
|
|
399
410
|
Examples:
|
|
400
411
|
response = fleet.update_task("my-task", prompt="New prompt text")
|
|
401
412
|
response = fleet.update_task("my-task", verifier_code="def verify(env): return True")
|
|
413
|
+
response = fleet.update_task("my-task", metadata={"seed": 42, "story": "Updated story"})
|
|
402
414
|
"""
|
|
403
415
|
from .global_client import get_client
|
|
404
416
|
|
|
405
417
|
client = get_client()
|
|
406
418
|
return client.update_task(
|
|
407
|
-
task_key=task_key, prompt=prompt, verifier_code=verifier_code
|
|
419
|
+
task_key=task_key, prompt=prompt, verifier_code=verifier_code, metadata=metadata
|
|
408
420
|
)
|
|
409
421
|
|
|
410
422
|
|
|
411
|
-
def get_task(
|
|
423
|
+
def get_task(
|
|
424
|
+
task_key: str, version_id: Optional[str] = None, team_id: Optional[str] = None
|
|
425
|
+
):
|
|
412
426
|
"""Convenience function to get a task by key and optional version.
|
|
413
427
|
|
|
414
428
|
Args:
|
|
@@ -11,9 +11,9 @@ examples/example_sync.py,sha256=EkuWmUzB1ZsBJQk6ZRflB793rKsuRHeSg5HJZHVhBB0,975
|
|
|
11
11
|
examples/example_task.py,sha256=dhG6STAkNsTdHs9cO1RFH9WfuvRmq5bRC211hTeFrk8,7088
|
|
12
12
|
examples/example_tasks.py,sha256=xTL8UWVAuolSX6swskfrAcmDrLIzn45dJ7YPWCwoEBU,514
|
|
13
13
|
examples/example_verifier.py,sha256=0vwNITIG3m4CkSPwIxNXcGx9TqrxEsCGqK2A8keKZMM,2392
|
|
14
|
-
examples/export_tasks.py,sha256=
|
|
14
|
+
examples/export_tasks.py,sha256=Sk2Id4i5tZeVs4ZFQoAn-ABFZv7ewyv4pqtZ-Z-tIEI,3574
|
|
15
15
|
examples/gemini_example.py,sha256=qj9WDazQTYNiRHNeUg9Tjkp33lJMwbx8gDfpFe1sDQo,16180
|
|
16
|
-
examples/import_tasks.py,sha256=
|
|
16
|
+
examples/import_tasks.py,sha256=McF5MbHenPZ7HTjQfNoHHevot0EBILMSsWtroicVT30,11619
|
|
17
17
|
examples/json_tasks_example.py,sha256=CYPESGGtOo0fmsDdLidujTfsE4QlJHw7rOhyVqPJ_Ls,5329
|
|
18
18
|
examples/nova_act_example.py,sha256=rH23Lp74Okf0rn8ynMdWjK2aviEf5NLPH4k_53Pyxho,831
|
|
19
19
|
examples/openai_example.py,sha256=dEWERrTEP5xBiGkLkQjBQGd2NqoxX6gcW6XteBPsWFQ,8231
|
|
@@ -23,22 +23,22 @@ examples/quickstart.py,sha256=1VT39IRRhemsJgxi0O0gprdpcw7HB4pYO97GAYagIcg,3788
|
|
|
23
23
|
examples/test_cdp_logging.py,sha256=AkCwQCgOTQEI8w3v0knWK_4eXMph7L9x07wj9yIYM10,2836
|
|
24
24
|
fleet/__init__.py,sha256=yC4HIcbtPAPOgI0lLri3l8nbXkNee9JOihKAc7SXYkY,4201
|
|
25
25
|
fleet/base.py,sha256=bc-340sTpq_DJs7yQ9d2pDWnmJFmA1SwDB9Lagvqtb4,9182
|
|
26
|
-
fleet/client.py,sha256=
|
|
26
|
+
fleet/client.py,sha256=niWdLmALTmLXHMnIZ4PEwyU6T28dxvtDXeLf-0DS_DE,33868
|
|
27
27
|
fleet/config.py,sha256=uY02ZKxVoXqVDta-0IMWaYJeE1CTXF_fA9NI6QUutmU,319
|
|
28
28
|
fleet/exceptions.py,sha256=fUmPwWhnT8SR97lYsRq0kLHQHKtSh2eJS0VQ2caSzEI,5055
|
|
29
29
|
fleet/global_client.py,sha256=frrDAFNM2ywN0JHLtlm9qbE1dQpnQJsavJpb7xSR_bU,1072
|
|
30
|
-
fleet/models.py,sha256=
|
|
31
|
-
fleet/tasks.py,sha256=
|
|
30
|
+
fleet/models.py,sha256=GUL61DOD4XDT588T1a-EV7R1kh1w4q_JRLnX1rEI5ek,13911
|
|
31
|
+
fleet/tasks.py,sha256=iwwvN2o1IWT-9zxmAMBHfT12rfX2Hm0DkfUlrPTs298,17956
|
|
32
32
|
fleet/types.py,sha256=L4Y82xICf1tzyCLqhLYUgEoaIIS5h9T05TyFNHSWs3s,652
|
|
33
33
|
fleet/_async/__init__.py,sha256=5oOTmh16UsPWL2gDKKWkj2j5WGNeUhMzbQFWjX21jsc,8310
|
|
34
34
|
fleet/_async/base.py,sha256=oisVTQsx0M_yTmyQJc3oij63uKZ97MHz-xYFsWXxQE8,9202
|
|
35
|
-
fleet/_async/client.py,sha256=
|
|
35
|
+
fleet/_async/client.py,sha256=ien5Xs1s4EetFAFExq7Vp_25lN2aVqEQyyDhYy3SJ00,34353
|
|
36
36
|
fleet/_async/exceptions.py,sha256=fUmPwWhnT8SR97lYsRq0kLHQHKtSh2eJS0VQ2caSzEI,5055
|
|
37
37
|
fleet/_async/global_client.py,sha256=4WskpLHbsDEgWW7hXMD09W-brkp4euy8w2ZJ88594rQ,1103
|
|
38
|
-
fleet/_async/models.py,sha256
|
|
39
|
-
fleet/_async/tasks.py,sha256=
|
|
38
|
+
fleet/_async/models.py,sha256=-3xv2QyoHsvYcWmdKLf9Z93md8XB17DBeJCxdRCB3bo,13571
|
|
39
|
+
fleet/_async/tasks.py,sha256=8d65DBMhmHNaLer1FhttUWK4WLS1SU5Hx9_nALMwM4Y,18002
|
|
40
40
|
fleet/_async/env/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
|
-
fleet/_async/env/client.py,sha256=
|
|
41
|
+
fleet/_async/env/client.py,sha256=Qv0QiW--ZMAMnz_IPYbYtokGP08x4PvSmkwde8rPCNk,2093
|
|
42
42
|
fleet/_async/instance/__init__.py,sha256=PtmJq8J8bh0SOQ2V55QURz5GJfobozwtQoqhaOk3_tI,515
|
|
43
43
|
fleet/_async/instance/base.py,sha256=3qUBuUR8OVS36LzdP6KyZzngtwPKYO09HoY6Ekxp-KA,1625
|
|
44
44
|
fleet/_async/instance/client.py,sha256=kcrmLZciQxvPSfTtbEq5LIbhscwDHg6WIiNcPyM4L9w,6085
|
|
@@ -50,8 +50,8 @@ fleet/_async/resources/sqlite.py,sha256=up_umepfyX9PDFsnmEMJLjsj7bLa6a3wizZOgMGk
|
|
|
50
50
|
fleet/_async/verifiers/__init__.py,sha256=1WTlCNq4tIFbbXaQu5Bf2WppZq0A8suhtZbxMTSOwxI,465
|
|
51
51
|
fleet/_async/verifiers/bundler.py,sha256=Sq0KkqEhM5Ng2x8R6Z4puXvQ8FMlEO7D3-ldBLktPi4,26205
|
|
52
52
|
fleet/_async/verifiers/verifier.py,sha256=IiHX028s6ux0kb2FR0Z5zJangl_IDh6cemXsUN2ktUU,14152
|
|
53
|
-
fleet/env/__init__.py,sha256=
|
|
54
|
-
fleet/env/client.py,sha256=
|
|
53
|
+
fleet/env/__init__.py,sha256=sSjD6vk8LzC_pxoXuRc8-ACqeX4PLm1FBWnWxpOhUS8,812
|
|
54
|
+
fleet/env/client.py,sha256=l2vI_BBrSXmyk2un3F1-QDcL-iv2OdBxTdYOdOioF0Q,1881
|
|
55
55
|
fleet/instance/__init__.py,sha256=CyWUkbGAK-DBPw4DC4AnCW-MqqheGhZMA5QSRVu-ws4,479
|
|
56
56
|
fleet/instance/base.py,sha256=OYqzBwZFfTX9wlBGSG5gljqj98NbiJeKIfFJ3uj5I4s,1587
|
|
57
57
|
fleet/instance/client.py,sha256=XM_Qmd7pUzC3-dCMTh6orTEsGeWJXbKueBlvcWcSUuI,5897
|
|
@@ -67,16 +67,14 @@ fleet/verifiers/code.py,sha256=A1i_UabZspbyj1awzKVQ_HRxgMO3fU7NbkxYyTrp7So,48
|
|
|
67
67
|
fleet/verifiers/db.py,sha256=LAh1HambBInH_D9q9E2Z41YNkCOI9JJfpWPFqztjpfQ,27922
|
|
68
68
|
fleet/verifiers/decorator.py,sha256=nAP3O8szXu7md_kpwpz91hGSUNEVLYjwZQZTkQlV1DM,3260
|
|
69
69
|
fleet/verifiers/parse.py,sha256=qz9AfJrTbjlg-LU-lE8Ciqi7Yt2a8-cs17FdpjTLhMk,8550
|
|
70
|
-
fleet/verifiers/parsing.py,sha256=EzlfHLogHPC1i5pfsF3ZCBJ8NY3s1uvz7v59CfcuMtI,3713
|
|
71
70
|
fleet/verifiers/sql_differ.py,sha256=TqTLWyK3uOyLbitT6HYzYEzuSFC39wcyhgk3rcm__k8,6525
|
|
72
71
|
fleet/verifiers/verifier.py,sha256=_lcxXVm8e0xRrK2gNJy9up7pW1zOkPRY5n5lQ85S8jg,14197
|
|
73
|
-
fleet_python-0.2.
|
|
72
|
+
fleet_python-0.2.68.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
|
|
74
73
|
scripts/fix_sync_imports.py,sha256=X9fWLTpiPGkSHsjyQUDepOJkxOqw1DPj7nd8wFlFqLQ,8368
|
|
75
74
|
scripts/unasync.py,sha256=vWVQxRWX8SRZO5cmzEhpvnG_REhCWXpidIGIpWmEcvI,696
|
|
76
75
|
tests/__init__.py,sha256=Re1SdyxH8NfyL1kjhi7SQkGP1mYeWB-D6UALqdIMd8I,35
|
|
77
76
|
tests/test_verifier_from_string.py,sha256=Lxi3TpFHFb-hG4-UhLKZJkqo84ax9YJY8G6beO-1erM,13581
|
|
78
|
-
|
|
79
|
-
fleet_python-0.2.
|
|
80
|
-
fleet_python-0.2.
|
|
81
|
-
fleet_python-0.2.
|
|
82
|
-
fleet_python-0.2.66b4.dist-info/RECORD,,
|
|
77
|
+
fleet_python-0.2.68.dist-info/METADATA,sha256=3OtKPA7XpF59sT-kEEUPKEdBFJp8GLVStE5kZJLJP84,3304
|
|
78
|
+
fleet_python-0.2.68.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
79
|
+
fleet_python-0.2.68.dist-info/top_level.txt,sha256=qb1zIbtEktyhRFZdqVytwg54l64qtoZL0wjHB4bUg3c,29
|
|
80
|
+
fleet_python-0.2.68.dist-info/RECORD,,
|
fleet/verifiers/parsing.py
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
"""Verifier code parsing and validation utilities."""
|
|
2
|
-
|
|
3
|
-
import ast
|
|
4
|
-
from typing import Set
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def parse_and_validate_verifier(code: str) -> str:
|
|
8
|
-
"""Parse and validate verifier code, returning the first function name.
|
|
9
|
-
|
|
10
|
-
This function ensures that the verifier code only contains safe declarative
|
|
11
|
-
statements and does not execute arbitrary code during import.
|
|
12
|
-
|
|
13
|
-
Args:
|
|
14
|
-
code: Python code string containing the verifier function
|
|
15
|
-
|
|
16
|
-
Returns:
|
|
17
|
-
Name of the first function found in the code
|
|
18
|
-
|
|
19
|
-
Raises:
|
|
20
|
-
ValueError: If code is invalid or contains unsafe statements
|
|
21
|
-
SyntaxError: If code has syntax errors
|
|
22
|
-
"""
|
|
23
|
-
try:
|
|
24
|
-
tree = ast.parse(code)
|
|
25
|
-
except SyntaxError as e:
|
|
26
|
-
raise SyntaxError(f"Syntax error in verifier code: {e}")
|
|
27
|
-
|
|
28
|
-
first_function_name = None
|
|
29
|
-
|
|
30
|
-
for node in tree.body:
|
|
31
|
-
# Check for function definitions
|
|
32
|
-
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
33
|
-
# Validate that decorators don't contain function calls
|
|
34
|
-
for decorator in node.decorator_list:
|
|
35
|
-
if _contains_call(decorator):
|
|
36
|
-
raise ValueError(
|
|
37
|
-
f"Line {node.lineno}: Function decorators with function calls "
|
|
38
|
-
f"are not allowed. Decorators execute during import and could "
|
|
39
|
-
f"run arbitrary code."
|
|
40
|
-
)
|
|
41
|
-
|
|
42
|
-
if first_function_name is None:
|
|
43
|
-
first_function_name = node.name
|
|
44
|
-
continue
|
|
45
|
-
|
|
46
|
-
# Allow imports
|
|
47
|
-
if isinstance(node, (ast.Import, ast.ImportFrom)):
|
|
48
|
-
continue
|
|
49
|
-
|
|
50
|
-
# Allow class definitions
|
|
51
|
-
if isinstance(node, ast.ClassDef):
|
|
52
|
-
continue
|
|
53
|
-
|
|
54
|
-
# Allow docstrings and other expression statements (but not calls)
|
|
55
|
-
if isinstance(node, ast.Expr):
|
|
56
|
-
if isinstance(node.value, ast.Constant):
|
|
57
|
-
# Docstring or constant expression - safe
|
|
58
|
-
continue
|
|
59
|
-
else:
|
|
60
|
-
# Check if it's a call or other dangerous expression
|
|
61
|
-
raise ValueError(
|
|
62
|
-
f"Line {node.lineno}: Expression statements that are not "
|
|
63
|
-
f"constants are not allowed at module level. Found: {ast.dump(node.value)}"
|
|
64
|
-
)
|
|
65
|
-
|
|
66
|
-
# Allow variable assignments, but check the value
|
|
67
|
-
if isinstance(node, (ast.Assign, ast.AnnAssign)):
|
|
68
|
-
# Check if the assignment value contains any function calls
|
|
69
|
-
if _contains_call(node.value if isinstance(node, ast.AnnAssign) else node.value):
|
|
70
|
-
raise ValueError(
|
|
71
|
-
f"Line {node.lineno}: Variable assignments with function calls "
|
|
72
|
-
f"are not allowed at module level. This prevents arbitrary code "
|
|
73
|
-
f"execution during import."
|
|
74
|
-
)
|
|
75
|
-
continue
|
|
76
|
-
|
|
77
|
-
# If we get here, it's an unsupported statement type
|
|
78
|
-
raise ValueError(
|
|
79
|
-
f"Line {node.lineno}: Unsupported statement type at module level: "
|
|
80
|
-
f"{node.__class__.__name__}. Only imports, function/class definitions, "
|
|
81
|
-
f"and constant assignments are allowed."
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
if first_function_name is None:
|
|
85
|
-
raise ValueError("No function found in verifier code")
|
|
86
|
-
|
|
87
|
-
return first_function_name
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
def _contains_call(node: ast.AST) -> bool:
|
|
91
|
-
"""Recursively check if an AST node contains any Call nodes.
|
|
92
|
-
|
|
93
|
-
Args:
|
|
94
|
-
node: AST node to check
|
|
95
|
-
|
|
96
|
-
Returns:
|
|
97
|
-
True if the node or any of its children is a Call node
|
|
98
|
-
"""
|
|
99
|
-
if isinstance(node, ast.Call):
|
|
100
|
-
return True
|
|
101
|
-
|
|
102
|
-
for child in ast.walk(node):
|
|
103
|
-
if isinstance(child, ast.Call):
|
|
104
|
-
return True
|
|
105
|
-
|
|
106
|
-
return False
|