fleet-python 0.2.71__tar.gz → 0.2.72b2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. {fleet_python-0.2.71/fleet_python.egg-info → fleet_python-0.2.72b2}/PKG-INFO +1 -1
  2. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/_async/client.py +163 -6
  3. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/_async/instance/client.py +19 -4
  4. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/_async/resources/sqlite.py +633 -38
  5. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/_async/tasks.py +5 -2
  6. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/client.py +183 -15
  7. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/instance/client.py +20 -5
  8. fleet_python-0.2.72b2/fleet/resources/sqlite.py +1350 -0
  9. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/tasks.py +5 -2
  10. {fleet_python-0.2.71 → fleet_python-0.2.72b2/fleet_python.egg-info}/PKG-INFO +1 -1
  11. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet_python.egg-info/SOURCES.txt +4 -0
  12. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/pyproject.toml +2 -1
  13. fleet_python-0.2.72b2/tests/test_app_method.py +85 -0
  14. fleet_python-0.2.72b2/tests/test_instance_dispatch.py +607 -0
  15. fleet_python-0.2.72b2/tests/test_sqlite_resource_dual_mode.py +263 -0
  16. fleet_python-0.2.72b2/tests/test_sqlite_shared_memory_behavior.py +117 -0
  17. fleet_python-0.2.71/fleet/resources/sqlite.py +0 -735
  18. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/LICENSE +0 -0
  19. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/README.md +0 -0
  20. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/examples/diff_example.py +0 -0
  21. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/examples/dsl_example.py +0 -0
  22. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/examples/example.py +0 -0
  23. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/examples/exampleResume.py +0 -0
  24. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/examples/example_account.py +0 -0
  25. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/examples/example_action_log.py +0 -0
  26. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/examples/example_client.py +0 -0
  27. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/examples/example_mcp_anthropic.py +0 -0
  28. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/examples/example_mcp_openai.py +0 -0
  29. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/examples/example_sync.py +0 -0
  30. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/examples/example_task.py +0 -0
  31. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/examples/example_tasks.py +0 -0
  32. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/examples/example_verifier.py +0 -0
  33. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/examples/export_tasks.py +0 -0
  34. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/examples/gemini_example.py +0 -0
  35. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/examples/import_tasks.py +0 -0
  36. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/examples/json_tasks_example.py +0 -0
  37. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/examples/nova_act_example.py +0 -0
  38. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/examples/openai_example.py +0 -0
  39. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/examples/openai_simple_example.py +0 -0
  40. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/examples/query_builder_example.py +0 -0
  41. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/examples/quickstart.py +0 -0
  42. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/examples/test_cdp_logging.py +0 -0
  43. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/__init__.py +0 -0
  44. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/_async/__init__.py +0 -0
  45. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/_async/base.py +0 -0
  46. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/_async/env/__init__.py +0 -0
  47. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/_async/env/client.py +0 -0
  48. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/_async/exceptions.py +0 -0
  49. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/_async/global_client.py +0 -0
  50. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/_async/instance/__init__.py +0 -0
  51. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/_async/instance/base.py +0 -0
  52. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/_async/models.py +0 -0
  53. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/_async/resources/__init__.py +0 -0
  54. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/_async/resources/base.py +0 -0
  55. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/_async/resources/browser.py +0 -0
  56. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/_async/resources/mcp.py +0 -0
  57. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/_async/verifiers/__init__.py +0 -0
  58. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/_async/verifiers/bundler.py +0 -0
  59. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/_async/verifiers/verifier.py +0 -0
  60. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/base.py +0 -0
  61. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/config.py +0 -0
  62. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/env/__init__.py +0 -0
  63. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/env/client.py +0 -0
  64. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/exceptions.py +0 -0
  65. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/global_client.py +0 -0
  66. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/instance/__init__.py +0 -0
  67. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/instance/base.py +0 -0
  68. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/instance/models.py +0 -0
  69. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/models.py +0 -0
  70. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/resources/__init__.py +0 -0
  71. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/resources/base.py +0 -0
  72. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/resources/browser.py +0 -0
  73. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/resources/mcp.py +0 -0
  74. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/types.py +0 -0
  75. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/verifiers/__init__.py +0 -0
  76. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/verifiers/bundler.py +0 -0
  77. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/verifiers/code.py +0 -0
  78. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/verifiers/db.py +0 -0
  79. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/verifiers/decorator.py +0 -0
  80. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/verifiers/parse.py +0 -0
  81. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/verifiers/sql_differ.py +0 -0
  82. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet/verifiers/verifier.py +0 -0
  83. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet_python.egg-info/dependency_links.txt +0 -0
  84. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet_python.egg-info/requires.txt +0 -0
  85. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/fleet_python.egg-info/top_level.txt +0 -0
  86. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/scripts/fix_sync_imports.py +0 -0
  87. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/scripts/unasync.py +0 -0
  88. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/setup.cfg +0 -0
  89. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/tests/__init__.py +0 -0
  90. {fleet_python-0.2.71 → fleet_python-0.2.72b2}/tests/test_verifier_from_string.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fleet-python
3
- Version: 0.2.71
3
+ Version: 0.2.72b2
4
4
  Summary: Python SDK for Fleet environments
5
5
  Author-email: Fleet AI <nic@fleet.so>
6
6
  License: Apache-2.0
@@ -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, AsyncWrapper
27
27
  from ..models import (
@@ -49,6 +49,11 @@ from .instance import (
49
49
  ResetResponse,
50
50
  ExecuteFunctionResponse,
51
51
  )
52
+ from ..instance.models import (
53
+ Resource as ResourceModel,
54
+ ResourceType,
55
+ ResourceMode,
56
+ )
52
57
  from ..config import (
53
58
  DEFAULT_MAX_RETRIES,
54
59
  DEFAULT_TIMEOUT,
@@ -310,11 +315,163 @@ class AsyncFleet:
310
315
  for instance_data in response.json()
311
316
  ]
312
317
 
313
- async def instance(self, instance_id: str) -> AsyncEnv:
314
- response = await self.client.request("GET", f"/v1/env/instances/{instance_id}")
315
- instance = AsyncEnv(client=self.client, **response.json())
316
- await instance.instance.load()
317
- return instance
318
+ async def instance(self, instance_id: Union[str, Dict[str, str]]) -> AsyncEnv:
319
+ """Create or connect to an environment instance.
320
+
321
+ Supports three modes based on input type:
322
+ 1. dict: Local filesystem mode - {"current": "./data.db", "seed": "./seed.db"}
323
+ 2. str starting with http:// or https://: Localhost/URL mode
324
+ 3. str (other): Remote cloud instance mode
325
+
326
+ Args:
327
+ instance_id: Instance identifier (str), URL (str starting with http://),
328
+ or local db mapping (dict)
329
+
330
+ Returns:
331
+ AsyncEnv: Environment instance
332
+ """
333
+ # Local filesystem mode - dict of resource names to file paths
334
+ if isinstance(instance_id, dict):
335
+ return self._create_local_instance(instance_id)
336
+
337
+ # Localhost/direct URL mode - string starting with http:// or https://
338
+ elif isinstance(instance_id, str) and instance_id.startswith(("http://", "https://")):
339
+ return self._create_url_instance(instance_id)
340
+
341
+ # Remote mode - existing behavior
342
+ else:
343
+ response = await self.client.request("GET", f"/v1/env/instances/{instance_id}")
344
+ instance = AsyncEnv(client=self.client, **response.json())
345
+ await instance.instance.load()
346
+ return instance
347
+
348
+ def _create_url_instance(self, base_url: str) -> AsyncEnv:
349
+ """Create instance connected to a direct URL (localhost or custom).
350
+
351
+ Args:
352
+ base_url: URL of the instance manager API
353
+
354
+ Returns:
355
+ AsyncEnv: Environment instance configured for URL mode
356
+ """
357
+ instance_client = AsyncInstanceClient(url=base_url, httpx_client=self._httpx_client)
358
+
359
+ # Create a minimal environment for URL mode
360
+ env = AsyncEnv(
361
+ client=self.client,
362
+ instance_id=base_url,
363
+ env_key="localhost",
364
+ version="",
365
+ status="running",
366
+ subdomain="localhost",
367
+ created_at="",
368
+ updated_at="",
369
+ terminated_at=None,
370
+ team_id="",
371
+ region="localhost",
372
+ env_variables=None,
373
+ data_key=None,
374
+ data_version=None,
375
+ urls=None,
376
+ health=None,
377
+ )
378
+ env._instance = instance_client
379
+ return env
380
+
381
+ @staticmethod
382
+ def _normalize_db_path(path: str) -> tuple[str, bool]:
383
+ """Normalize database path and detect if it's in-memory.
384
+
385
+ Args:
386
+ path: Database path - can be:
387
+ - File path: "./data.db"
388
+ - Plain memory: ":memory:"
389
+ - Named memory: ":memory:namespace"
390
+ - URI: "file:name?mode=memory&cache=shared"
391
+
392
+ Returns:
393
+ Tuple of (normalized_path, is_memory)
394
+ """
395
+ import uuid
396
+ import sqlite3
397
+
398
+ if path == ":memory:":
399
+ # Plain :memory: - create unique namespace
400
+ name = f"mem_{uuid.uuid4().hex[:8]}"
401
+ return f"file:{name}?mode=memory&cache=shared", True
402
+ elif path.startswith(":memory:"):
403
+ # Named memory: :memory:current -> file:current?mode=memory&cache=shared
404
+ namespace = path[8:] # Remove ":memory:" prefix
405
+ return f"file:{namespace}?mode=memory&cache=shared", True
406
+ elif "mode=memory" in path:
407
+ # Already a proper memory URI
408
+ return path, True
409
+ else:
410
+ # Regular file path
411
+ return path, False
412
+
413
+ def _create_local_instance(self, dbs: Dict[str, str]) -> AsyncEnv:
414
+ """Create instance with local file-based or in-memory SQLite resources.
415
+
416
+ Args:
417
+ dbs: Map of resource names to paths (e.g., {"current": "./data.db"} or
418
+ {"current": ":memory:current"})
419
+
420
+ Returns:
421
+ AsyncEnv: Environment instance configured for local mode
422
+ """
423
+ import sqlite3
424
+
425
+ instance_client = AsyncInstanceClient(url="local://", httpx_client=None)
426
+ instance_client._resources = [] # Mark as loaded
427
+ instance_client._memory_anchors = {} # Store anchor connections for in-memory DBs
428
+
429
+ # Store creation parameters for local AsyncSQLiteResources
430
+ # This allows db() to create new instances each time (matching HTTP mode behavior)
431
+ for name, path in dbs.items():
432
+ # Normalize path and detect if it's in-memory
433
+ normalized_path, is_memory = self._normalize_db_path(path)
434
+
435
+ # Create anchor connection for in-memory databases
436
+ # This keeps the database alive as long as the env exists
437
+ if is_memory:
438
+ anchor_conn = sqlite3.connect(normalized_path, uri=True)
439
+ instance_client._memory_anchors[name] = anchor_conn
440
+
441
+ resource_model = ResourceModel(
442
+ name=name,
443
+ type=ResourceType.db,
444
+ mode=ResourceMode.rw,
445
+ label=f"Local: {path}",
446
+ )
447
+ instance_client._resources_state[ResourceType.db.value][name] = {
448
+ 'type': 'local',
449
+ 'resource_model': resource_model,
450
+ 'db_path': normalized_path,
451
+ 'is_memory': is_memory
452
+ }
453
+
454
+ # Create a minimal environment for local mode
455
+ env = AsyncEnv(
456
+ client=self.client,
457
+ instance_id="local",
458
+ env_key="local",
459
+ version="",
460
+ status="running",
461
+ subdomain="local",
462
+ created_at="",
463
+ updated_at="",
464
+ terminated_at=None,
465
+ team_id="",
466
+ region="local",
467
+ env_variables=None,
468
+ data_key=None,
469
+ data_version=None,
470
+ urls=None,
471
+ health=None,
472
+ )
473
+ env._instance = instance_client
474
+ return env
318
475
 
319
476
  async def check_bundle_exists(self, bundle_hash: str) -> VerifiersCheckResponse:
320
477
  return await _check_bundle_exists(self.client, bundle_hash)
@@ -85,9 +85,17 @@ class AsyncInstanceClient:
85
85
  Returns:
86
86
  An SQLite database resource for the given database name
87
87
  """
88
- return AsyncSQLiteResource(
89
- self._resources_state[ResourceType.db.value][name], self.client
90
- )
88
+ resource_info = self._resources_state[ResourceType.db.value][name]
89
+ # Local mode - resource_info is a dict with creation parameters
90
+ if isinstance(resource_info, dict) and resource_info.get('type') == 'local':
91
+ # Create new instance each time (matching HTTP mode behavior)
92
+ return AsyncSQLiteResource(
93
+ resource_info['resource_model'],
94
+ client=None,
95
+ db_path=resource_info['db_path']
96
+ )
97
+ # HTTP mode - resource_info is a ResourceModel, create new wrapper
98
+ return AsyncSQLiteResource(resource_info, self.client)
91
99
 
92
100
  def browser(self, name: str) -> AsyncBrowserResource:
93
101
  return AsyncBrowserResource(
@@ -177,10 +185,17 @@ class AsyncInstanceClient:
177
185
  response = await self.client.request("GET", "/health")
178
186
  return HealthResponse(**response.json())
179
187
 
188
+ def close(self):
189
+ """Close anchor connections for in-memory databases."""
190
+ if hasattr(self, '_memory_anchors'):
191
+ for conn in self._memory_anchors.values():
192
+ conn.close()
193
+ self._memory_anchors.clear()
194
+
180
195
  async def __aenter__(self):
181
196
  """Async context manager entry."""
182
197
  return self
183
198
 
184
199
  async def __aexit__(self, exc_type, exc_val, exc_tb):
185
200
  """Async context manager exit."""
186
- await self.close()
201
+ self.close()