fleet-python 0.2.67__py3-none-any.whl → 0.2.69b2__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.

fleet/client.py CHANGED
@@ -21,7 +21,7 @@ import httpx
21
21
  import json
22
22
  import logging
23
23
  import os
24
- from typing import List, Optional, Dict, Any, TYPE_CHECKING
24
+ from typing import List, Optional, Dict, Any, TYPE_CHECKING, Union
25
25
 
26
26
  from .base import EnvironmentBase, SyncWrapper
27
27
  from .models import (
@@ -47,6 +47,11 @@ from .instance import (
47
47
  ResetResponse,
48
48
  ExecuteFunctionResponse,
49
49
  )
50
+ from .instance.models import (
51
+ Resource as ResourceModel,
52
+ ResourceType,
53
+ ResourceMode,
54
+ )
50
55
  from .config import (
51
56
  DEFAULT_MAX_RETRIES,
52
57
  DEFAULT_TIMEOUT,
@@ -212,6 +217,7 @@ class Fleet:
212
217
  env_variables: Optional[Dict[str, Any]] = None,
213
218
  image_type: Optional[str] = None,
214
219
  ttl_seconds: Optional[int] = None,
220
+ run_id: Optional[str] = None,
215
221
  ) -> SyncEnv:
216
222
  if ":" in env_key:
217
223
  env_key_part, env_version = env_key.split(":", 1)
@@ -247,6 +253,7 @@ class Fleet:
247
253
  image_type=image_type,
248
254
  created_from="sdk",
249
255
  ttl_seconds=ttl_seconds,
256
+ run_id=run_id,
250
257
  )
251
258
 
252
259
  # Only use region-specific base URL if no custom base URL is set
@@ -269,13 +276,15 @@ class Fleet:
269
276
  return self.make(env_key=f"{task.env_id}:{task.version}")
270
277
 
271
278
  def instances(
272
- self, status: Optional[str] = None, region: Optional[str] = None
279
+ self, status: Optional[str] = None, region: Optional[str] = None, run_id: Optional[str] = None
273
280
  ) -> List[SyncEnv]:
274
281
  params = {}
275
282
  if status:
276
283
  params["status"] = status
277
284
  if region:
278
285
  params["region"] = region
286
+ if run_id:
287
+ params["run_id"] = run_id
279
288
 
280
289
  response = self.client.request("GET", "/v1/env/instances", params=params)
281
290
  return [
@@ -283,11 +292,163 @@ class Fleet:
283
292
  for instance_data in response.json()
284
293
  ]
285
294
 
286
- def instance(self, instance_id: str) -> SyncEnv:
287
- response = self.client.request("GET", f"/v1/env/instances/{instance_id}")
288
- instance = SyncEnv(client=self.client, **response.json())
289
- instance.instance.load()
290
- return instance
295
+ def instance(self, instance_id: Union[str, Dict[str, str]]) -> SyncEnv:
296
+ """Create or connect to an environment instance.
297
+
298
+ Supports three modes based on input type:
299
+ 1. dict: Local filesystem mode - {"current": "./data.db", "seed": "./seed.db"}
300
+ 2. str starting with http:// or https://: Localhost/URL mode
301
+ 3. str (other): Remote cloud instance mode
302
+
303
+ Args:
304
+ instance_id: Instance identifier (str), URL (str starting with http://),
305
+ or local db mapping (dict)
306
+
307
+ Returns:
308
+ SyncEnv: Environment instance
309
+ """
310
+ # Local filesystem mode - dict of resource names to file paths
311
+ if isinstance(instance_id, dict):
312
+ return self._create_local_instance(instance_id)
313
+
314
+ # Localhost/direct URL mode - string starting with http:// or https://
315
+ elif isinstance(instance_id, str) and instance_id.startswith(("http://", "https://")):
316
+ return self._create_url_instance(instance_id)
317
+
318
+ # Remote mode - existing behavior
319
+ else:
320
+ response = self.client.request("GET", f"/v1/env/instances/{instance_id}")
321
+ instance = SyncEnv(client=self.client, **response.json())
322
+ instance.instance.load()
323
+ return instance
324
+
325
+ def _create_url_instance(self, base_url: str) -> SyncEnv:
326
+ """Create instance connected to a direct URL (localhost or custom).
327
+
328
+ Args:
329
+ base_url: URL of the instance manager API
330
+
331
+ Returns:
332
+ SyncEnv: Environment instance configured for URL mode
333
+ """
334
+ instance_client = InstanceClient(url=base_url, httpx_client=self._httpx_client)
335
+
336
+ # Create a minimal environment for URL mode
337
+ env = SyncEnv(
338
+ client=self.client,
339
+ instance_id=base_url,
340
+ env_key="localhost",
341
+ version="",
342
+ status="running",
343
+ subdomain="localhost",
344
+ created_at="",
345
+ updated_at="",
346
+ terminated_at=None,
347
+ team_id="",
348
+ region="localhost",
349
+ env_variables=None,
350
+ data_key=None,
351
+ data_version=None,
352
+ urls=None,
353
+ health=None,
354
+ )
355
+ env._instance = instance_client
356
+ return env
357
+
358
+ @staticmethod
359
+ def _normalize_db_path(path: str) -> tuple[str, bool]:
360
+ """Normalize database path and detect if it's in-memory.
361
+
362
+ Args:
363
+ path: Database path - can be:
364
+ - File path: "./data.db"
365
+ - Plain memory: ":memory:"
366
+ - Named memory: ":memory:namespace"
367
+ - URI: "file:name?mode=memory&cache=shared"
368
+
369
+ Returns:
370
+ Tuple of (normalized_path, is_memory)
371
+ """
372
+ import uuid
373
+ import sqlite3
374
+
375
+ if path == ":memory:":
376
+ # Plain :memory: - create unique namespace
377
+ name = f"mem_{uuid.uuid4().hex[:8]}"
378
+ return f"file:{name}?mode=memory&cache=shared", True
379
+ elif path.startswith(":memory:"):
380
+ # Named memory: :memory:current -> file:current?mode=memory&cache=shared
381
+ namespace = path[8:] # Remove ":memory:" prefix
382
+ return f"file:{namespace}?mode=memory&cache=shared", True
383
+ elif "mode=memory" in path:
384
+ # Already a proper memory URI
385
+ return path, True
386
+ else:
387
+ # Regular file path
388
+ return path, False
389
+
390
+ def _create_local_instance(self, dbs: Dict[str, str]) -> SyncEnv:
391
+ """Create instance with local file-based or in-memory SQLite resources.
392
+
393
+ Args:
394
+ dbs: Map of resource names to paths (e.g., {"current": "./data.db"} or
395
+ {"current": ":memory:current"})
396
+
397
+ Returns:
398
+ SyncEnv: Environment instance configured for local mode
399
+ """
400
+ import sqlite3
401
+
402
+ instance_client = InstanceClient(url="local://", httpx_client=None)
403
+ instance_client._resources = [] # Mark as loaded
404
+ instance_client._memory_anchors = {} # Store anchor connections for in-memory DBs
405
+
406
+ # Store creation parameters for local SQLiteResources
407
+ # This allows db() to create new instances each time (matching HTTP mode behavior)
408
+ for name, path in dbs.items():
409
+ # Normalize path and detect if it's in-memory
410
+ normalized_path, is_memory = self._normalize_db_path(path)
411
+
412
+ # Create anchor connection for in-memory databases
413
+ # This keeps the database alive as long as the env exists
414
+ if is_memory:
415
+ anchor_conn = sqlite3.connect(normalized_path, uri=True)
416
+ instance_client._memory_anchors[name] = anchor_conn
417
+
418
+ resource_model = ResourceModel(
419
+ name=name,
420
+ type=ResourceType.db,
421
+ mode=ResourceMode.rw,
422
+ label=f"Local: {path}",
423
+ )
424
+ instance_client._resources_state[ResourceType.db.value][name] = {
425
+ 'type': 'local',
426
+ 'resource_model': resource_model,
427
+ 'db_path': normalized_path,
428
+ 'is_memory': is_memory
429
+ }
430
+
431
+ # Create a minimal environment for local mode
432
+ env = SyncEnv(
433
+ client=self.client,
434
+ instance_id="local",
435
+ env_key="local",
436
+ version="",
437
+ status="running",
438
+ subdomain="local",
439
+ created_at="",
440
+ updated_at="",
441
+ terminated_at=None,
442
+ team_id="",
443
+ region="local",
444
+ env_variables=None,
445
+ data_key=None,
446
+ data_version=None,
447
+ urls=None,
448
+ health=None,
449
+ )
450
+ env._instance = instance_client
451
+ return env
291
452
 
292
453
  def check_bundle_exists(self, bundle_hash: str) -> VerifiersCheckResponse:
293
454
  return _check_bundle_exists(self.client, bundle_hash)
@@ -300,6 +461,28 @@ class Fleet:
300
461
  def delete(self, instance_id: str) -> InstanceResponse:
301
462
  return _delete_instance(self.client, instance_id)
302
463
 
464
+ def close(self, instance_id: str) -> InstanceResponse:
465
+ """Close (delete) a specific instance by ID.
466
+
467
+ Args:
468
+ instance_id: The instance ID to close
469
+
470
+ Returns:
471
+ InstanceResponse containing the deleted instance details
472
+ """
473
+ return _delete_instance(self.client, instance_id)
474
+
475
+ def close_all(self, run_id: str) -> List[InstanceResponse]:
476
+ """Close (delete) all instances associated with a run_id.
477
+
478
+ Args:
479
+ run_id: The run ID whose instances should be closed
480
+
481
+ Returns:
482
+ List[InstanceResponse] containing the deleted instances
483
+ """
484
+ return _delete_instances_by_run_id(self.client, run_id)
485
+
303
486
  def load_tasks_from_file(self, filename: str) -> List[Task]:
304
487
  with open(filename, "r", encoding="utf-8") as f:
305
488
  tasks_data = f.read()
@@ -810,6 +993,11 @@ def _delete_instance(client: SyncWrapper, instance_id: str) -> InstanceResponse:
810
993
  return InstanceResponse(**response.json())
811
994
 
812
995
 
996
+ def _delete_instances_by_run_id(client: SyncWrapper, run_id: str) -> List[InstanceResponse]:
997
+ response = client.request("DELETE", f"/v1/env/instances/run/{run_id}")
998
+ return [InstanceResponse(**instance_data) for instance_data in response.json()]
999
+
1000
+
813
1001
  def _check_bundle_exists(
814
1002
  client: SyncWrapper, bundle_hash: str
815
1003
  ) -> VerifiersCheckResponse:
fleet/env/__init__.py CHANGED
@@ -7,6 +7,8 @@ from .client import (
7
7
  list_regions,
8
8
  get,
9
9
  list_instances,
10
+ close,
11
+ close_all,
10
12
  account,
11
13
  )
12
14
 
@@ -17,6 +19,8 @@ from .._async.env.client import (
17
19
  list_regions_async,
18
20
  get_async,
19
21
  list_instances_async,
22
+ close_async,
23
+ close_all_async,
20
24
  account_async,
21
25
  )
22
26
 
@@ -27,11 +31,15 @@ __all__ = [
27
31
  "list_regions",
28
32
  "list_instances",
29
33
  "get",
34
+ "close",
35
+ "close_all",
30
36
  "make_async",
31
37
  "list_envs_async",
32
38
  "list_regions_async",
33
39
  "list_instances_async",
34
40
  "get_async",
41
+ "close_async",
42
+ "close_all_async",
35
43
  "account",
36
44
  "account_async",
37
45
  ]
fleet/env/client.py CHANGED
@@ -1,5 +1,5 @@
1
1
  from ..client import Fleet, SyncEnv, Task
2
- from ..models import Environment as EnvironmentModel, AccountResponse
2
+ from ..models import Environment as EnvironmentModel, AccountResponse, InstanceResponse
3
3
  from typing import List, Optional, Dict, Any
4
4
 
5
5
 
@@ -10,6 +10,7 @@ def make(
10
10
  env_variables: Optional[Dict[str, Any]] = None,
11
11
  image_type: Optional[str] = None,
12
12
  ttl_seconds: Optional[int] = None,
13
+ run_id: Optional[str] = None,
13
14
  ) -> SyncEnv:
14
15
  return Fleet().make(
15
16
  env_key,
@@ -18,6 +19,7 @@ def make(
18
19
  env_variables=env_variables,
19
20
  image_type=image_type,
20
21
  ttl_seconds=ttl_seconds,
22
+ run_id=run_id,
21
23
  )
22
24
 
23
25
 
@@ -34,14 +36,38 @@ def list_regions() -> List[str]:
34
36
 
35
37
 
36
38
  def list_instances(
37
- status: Optional[str] = None, region: Optional[str] = None
39
+ status: Optional[str] = None, region: Optional[str] = None, run_id: Optional[str] = None
38
40
  ) -> List[SyncEnv]:
39
- return Fleet().instances(status=status, region=region)
41
+ return Fleet().instances(status=status, region=region, run_id=run_id)
40
42
 
41
43
 
42
44
  def get(instance_id: str) -> SyncEnv:
43
45
  return Fleet().instance(instance_id)
44
46
 
45
47
 
48
+ def close(instance_id: str) -> InstanceResponse:
49
+ """Close (delete) a specific instance by ID.
50
+
51
+ Args:
52
+ instance_id: The instance ID to close
53
+
54
+ Returns:
55
+ InstanceResponse containing the deleted instance details
56
+ """
57
+ return Fleet().close(instance_id)
58
+
59
+
60
+ def close_all(run_id: str) -> List[InstanceResponse]:
61
+ """Close (delete) all instances associated with a run_id.
62
+
63
+ Args:
64
+ run_id: The run ID whose instances should be closed
65
+
66
+ Returns:
67
+ List[InstanceResponse] containing the deleted instances
68
+ """
69
+ return Fleet().close_all(run_id)
70
+
71
+
46
72
  def account() -> AccountResponse:
47
73
  return Fleet().account()
fleet/instance/client.py CHANGED
@@ -83,9 +83,17 @@ class InstanceClient:
83
83
  Returns:
84
84
  An SQLite database resource for the given database name
85
85
  """
86
- return SQLiteResource(
87
- self._resources_state[ResourceType.db.value][name], self.client
88
- )
86
+ resource_info = self._resources_state[ResourceType.db.value][name]
87
+ # Local mode - resource_info is a dict with creation parameters
88
+ if isinstance(resource_info, dict) and resource_info.get('type') == 'local':
89
+ # Create new instance each time (matching HTTP mode behavior)
90
+ return SQLiteResource(
91
+ resource_info['resource_model'],
92
+ client=None,
93
+ db_path=resource_info['db_path']
94
+ )
95
+ # HTTP mode - resource_info is a ResourceModel, create new wrapper
96
+ return SQLiteResource(resource_info, self.client)
89
97
 
90
98
  def browser(self, name: str) -> BrowserResource:
91
99
  return BrowserResource(
@@ -175,10 +183,17 @@ class InstanceClient:
175
183
  response = self.client.request("GET", "/health")
176
184
  return HealthResponse(**response.json())
177
185
 
186
+ def close(self):
187
+ """Close anchor connections for in-memory databases."""
188
+ if hasattr(self, '_memory_anchors'):
189
+ for conn in self._memory_anchors.values():
190
+ conn.close()
191
+ self._memory_anchors.clear()
192
+
178
193
  def __enter__(self):
179
- """Async context manager entry."""
194
+ """Context manager entry."""
180
195
  return self
181
196
 
182
197
  def __exit__(self, exc_type, exc_val, exc_tb):
183
- """Async context manager exit."""
198
+ """Context manager exit."""
184
199
  self.close()
fleet/models.py CHANGED
@@ -51,6 +51,7 @@ class Instance(BaseModel):
51
51
  team_id: str = Field(..., title="Team Id")
52
52
  region: str = Field(..., title="Region")
53
53
  env_variables: Optional[Dict[str, Any]] = Field(None, title="Env Variables")
54
+ run_id: Optional[str] = Field(None, title="Run Id")
54
55
 
55
56
 
56
57
  class InstanceRequest(BaseModel):
@@ -363,6 +364,7 @@ class InstanceResponse(BaseModel):
363
364
  data_version: Optional[str] = Field(None, title="Data Version")
364
365
  urls: Optional[InstanceURLs] = Field(None, title="Urls")
365
366
  health: Optional[bool] = Field(None, title="Health")
367
+ run_id: Optional[str] = Field(None, title="Run Id")
366
368
 
367
369
 
368
370
  class AccountResponse(BaseModel):
fleet/resources/sqlite.py CHANGED
@@ -675,17 +675,97 @@ class SyncQueryBuilder:
675
675
 
676
676
 
677
677
  class SQLiteResource(Resource):
678
- def __init__(self, resource: ResourceModel, client: "SyncWrapper"):
678
+ def __init__(
679
+ self,
680
+ resource: ResourceModel,
681
+ client: Optional["SyncWrapper"] = None,
682
+ db_path: Optional[str] = None,
683
+ ):
679
684
  super().__init__(resource)
680
685
  self.client = client
686
+ self.db_path = db_path
687
+ self._mode = "direct" if db_path else "http"
688
+
689
+ @property
690
+ def mode(self) -> str:
691
+ """Return the mode of this resource: 'direct' (local file) or 'http' (remote API)."""
692
+ return self._mode
681
693
 
682
694
  def describe(self) -> DescribeResponse:
683
695
  """Describe the SQLite database schema."""
696
+ if self._mode == "direct":
697
+ return self._describe_direct()
698
+ else:
699
+ return self._describe_http()
700
+
701
+ def _describe_http(self) -> DescribeResponse:
702
+ """Describe database schema via HTTP API."""
684
703
  response = self.client.request(
685
704
  "GET", f"/resources/sqlite/{self.resource.name}/describe"
686
705
  )
687
706
  return DescribeResponse(**response.json())
688
707
 
708
+ def _describe_direct(self) -> DescribeResponse:
709
+ """Describe database schema from local file or in-memory database."""
710
+ try:
711
+ # Check if we need URI mode (for shared memory databases)
712
+ use_uri = 'mode=memory' in self.db_path
713
+ conn = sqlite3.connect(self.db_path, uri=use_uri)
714
+ cursor = conn.cursor()
715
+
716
+ # Get all tables
717
+ cursor.execute(
718
+ "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
719
+ )
720
+ table_names = [row[0] for row in cursor.fetchall()]
721
+
722
+ tables = []
723
+ for table_name in table_names:
724
+ # Get table info
725
+ cursor.execute(f"PRAGMA table_info({table_name})")
726
+ columns = cursor.fetchall()
727
+
728
+ # Get CREATE TABLE SQL
729
+ cursor.execute(
730
+ f"SELECT sql FROM sqlite_master WHERE type='table' AND name=?",
731
+ (table_name,)
732
+ )
733
+ sql_row = cursor.fetchone()
734
+ create_sql = sql_row[0] if sql_row else ""
735
+
736
+ table_schema = {
737
+ "name": table_name,
738
+ "sql": create_sql,
739
+ "columns": [
740
+ {
741
+ "name": col[1],
742
+ "type": col[2],
743
+ "notnull": bool(col[3]),
744
+ "default_value": col[4],
745
+ "primary_key": col[5] > 0,
746
+ }
747
+ for col in columns
748
+ ],
749
+ }
750
+ tables.append(table_schema)
751
+
752
+ conn.close()
753
+
754
+ return DescribeResponse(
755
+ success=True,
756
+ resource_name=self.resource.name,
757
+ tables=tables,
758
+ message="Schema retrieved from local file",
759
+ )
760
+ except Exception as e:
761
+ return DescribeResponse(
762
+ success=False,
763
+ resource_name=self.resource.name,
764
+ tables=None,
765
+ error=str(e),
766
+ message=f"Failed to describe database: {str(e)}",
767
+ )
768
+
689
769
  def query(self, query: str, args: Optional[List[Any]] = None) -> QueryResponse:
690
770
  return self._query(query, args, read_only=True)
691
771
 
@@ -695,6 +775,15 @@ class SQLiteResource(Resource):
695
775
  def _query(
696
776
  self, query: str, args: Optional[List[Any]] = None, read_only: bool = True
697
777
  ) -> QueryResponse:
778
+ if self._mode == "direct":
779
+ return self._query_direct(query, args, read_only)
780
+ else:
781
+ return self._query_http(query, args, read_only)
782
+
783
+ def _query_http(
784
+ self, query: str, args: Optional[List[Any]] = None, read_only: bool = True
785
+ ) -> QueryResponse:
786
+ """Execute query via HTTP API."""
698
787
  request = QueryRequest(query=query, args=args, read_only=read_only)
699
788
  response = self.client.request(
700
789
  "POST",
@@ -703,6 +792,59 @@ class SQLiteResource(Resource):
703
792
  )
704
793
  return QueryResponse(**response.json())
705
794
 
795
+ def _query_direct(
796
+ self, query: str, args: Optional[List[Any]] = None, read_only: bool = True
797
+ ) -> QueryResponse:
798
+ """Execute query directly on local SQLite file or in-memory database."""
799
+ try:
800
+ # Check if we need URI mode (for shared memory databases)
801
+ use_uri = 'mode=memory' in self.db_path
802
+ conn = sqlite3.connect(self.db_path, uri=use_uri)
803
+ cursor = conn.cursor()
804
+
805
+ # Execute the query
806
+ if args:
807
+ cursor.execute(query, args)
808
+ else:
809
+ cursor.execute(query)
810
+
811
+ # For write operations, commit the transaction
812
+ if not read_only:
813
+ conn.commit()
814
+
815
+ # Get column names if available
816
+ columns = [desc[0] for desc in cursor.description] if cursor.description else []
817
+
818
+ # Fetch results for SELECT queries
819
+ rows = []
820
+ rows_affected = 0
821
+ last_insert_id = None
822
+
823
+ if cursor.description: # SELECT query
824
+ rows = cursor.fetchall()
825
+ else: # INSERT/UPDATE/DELETE
826
+ rows_affected = cursor.rowcount
827
+ last_insert_id = cursor.lastrowid if cursor.lastrowid else None
828
+
829
+ conn.close()
830
+
831
+ return QueryResponse(
832
+ success=True,
833
+ columns=columns if columns else None,
834
+ rows=rows if rows else None,
835
+ rows_affected=rows_affected if rows_affected > 0 else None,
836
+ last_insert_id=last_insert_id,
837
+ message="Query executed successfully",
838
+ )
839
+ except Exception as e:
840
+ return QueryResponse(
841
+ success=False,
842
+ columns=None,
843
+ rows=None,
844
+ error=str(e),
845
+ message=f"Query failed: {str(e)}",
846
+ )
847
+
706
848
  def table(self, table_name: str) -> SyncQueryBuilder:
707
849
  """Create a query builder for the specified table."""
708
850
  return SyncQueryBuilder(self, table_name)
fleet/tasks.py CHANGED
@@ -207,18 +207,20 @@ class Task(BaseModel):
207
207
  region: Optional[str] = None,
208
208
  image_type: Optional[str] = None,
209
209
  ttl_seconds: Optional[int] = None,
210
+ run_id: Optional[str] = None,
210
211
  ):
211
212
  """Create an environment instance for this task's environment.
212
213
 
213
214
  Alias for make() method. Uses the task's env_id (and version if present) to create the env.
214
215
  """
215
- return self.make(region=region, image_type=image_type, ttl_seconds=ttl_seconds)
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
 
@@ -281,8 +286,11 @@ def verifier_from_string(
281
286
  # Remove lines like: @verifier(key="...")
282
287
  cleaned_code = re.sub(r"@verifier\([^)]*\)\s*\n", "", verifier_func)
283
288
  # Also remove the verifier import if present
284
- cleaned_code = re.sub(r"from fleet import.*verifier.*\n", "", cleaned_code)
285
- cleaned_code = re.sub(r"import.*verifier.*\n", "", cleaned_code)
289
+ # Use MULTILINE flag to match beginning of lines with ^
290
+ cleaned_code = re.sub(r"^from fleet\.verifiers.*import.*verifier.*$\n?", "", cleaned_code, flags=re.MULTILINE)
291
+ cleaned_code = re.sub(r"^from fleet import verifier.*$\n?", "", cleaned_code, flags=re.MULTILINE)
292
+ cleaned_code = re.sub(r"^import fleet\.verifiers.*$\n?", "", cleaned_code, flags=re.MULTILINE)
293
+ cleaned_code = re.sub(r"^import fleet$\n?", "", cleaned_code, flags=re.MULTILINE)
286
294
 
287
295
  # Create a globals namespace with all required imports
288
296
  exec_globals = globals().copy()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fleet-python
3
- Version: 0.2.67
3
+ Version: 0.2.69b2
4
4
  Summary: Python SDK for Fleet environments
5
5
  Author-email: Fleet AI <nic@fleet.so>
6
6
  License: Apache-2.0