fleet-python 0.1.0__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/facets/base.py ADDED
@@ -0,0 +1,223 @@
1
+ """Fleet SDK Base Facet Classes."""
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import Any, Dict, Optional, TYPE_CHECKING
5
+ from urllib.parse import urlparse
6
+
7
+ if TYPE_CHECKING:
8
+ from ..env.base import Environment
9
+
10
+
11
+ class Facet(ABC):
12
+ """Base class for all facets in Fleet environments."""
13
+
14
+ def __init__(self, uri: str, environment: "Environment"):
15
+ self.uri = uri
16
+ self.environment = environment
17
+ self._parsed_uri = urlparse(uri)
18
+ self._scheme = self._parsed_uri.scheme
19
+ self._netloc = self._parsed_uri.netloc
20
+ self._path = self._parsed_uri.path
21
+ self._params = self._parsed_uri.params
22
+ self._query = self._parsed_uri.query
23
+ self._fragment = self._parsed_uri.fragment
24
+
25
+ @property
26
+ def scheme(self) -> str:
27
+ """Get the URI scheme (e.g., 'sqlite', 'browser', 'file')."""
28
+ return self._scheme
29
+
30
+ @property
31
+ def netloc(self) -> str:
32
+ """Get the URI network location."""
33
+ return self._netloc
34
+
35
+ @property
36
+ def path(self) -> str:
37
+ """Get the URI path."""
38
+ return self._path
39
+
40
+ @abstractmethod
41
+ async def initialize(self) -> None:
42
+ """Initialize the facet."""
43
+ pass
44
+
45
+ @abstractmethod
46
+ async def close(self) -> None:
47
+ """Close the facet and clean up resources."""
48
+ pass
49
+
50
+ def __repr__(self) -> str:
51
+ return f"{self.__class__.__name__}(uri='{self.uri}')"
52
+
53
+
54
+ class DatabaseFacet(Facet):
55
+ """Base class for database facets."""
56
+
57
+ @abstractmethod
58
+ async def exec(self, query: str, params: Optional[Dict[str, Any]] = None) -> Any:
59
+ """Execute a database query.
60
+
61
+ Args:
62
+ query: SQL query to execute
63
+ params: Query parameters
64
+
65
+ Returns:
66
+ Query result
67
+ """
68
+ pass
69
+
70
+ @abstractmethod
71
+ async def fetch(self, query: str, params: Optional[Dict[str, Any]] = None) -> Any:
72
+ """Fetch results from a database query.
73
+
74
+ Args:
75
+ query: SQL query to execute
76
+ params: Query parameters
77
+
78
+ Returns:
79
+ Query results
80
+ """
81
+ pass
82
+
83
+
84
+ class BrowserFacet(Facet):
85
+ """Base class for browser facets."""
86
+
87
+ @abstractmethod
88
+ async def get_dom(self) -> Dict[str, Any]:
89
+ """Get the current DOM structure.
90
+
91
+ Returns:
92
+ DOM structure as dictionary
93
+ """
94
+ pass
95
+
96
+ @abstractmethod
97
+ async def get_element(self, selector: str) -> Optional[Dict[str, Any]]:
98
+ """Get an element by CSS selector.
99
+
100
+ Args:
101
+ selector: CSS selector
102
+
103
+ Returns:
104
+ Element data or None if not found
105
+ """
106
+ pass
107
+
108
+ @abstractmethod
109
+ async def get_elements(self, selector: str) -> list[Dict[str, Any]]:
110
+ """Get elements by CSS selector.
111
+
112
+ Args:
113
+ selector: CSS selector
114
+
115
+ Returns:
116
+ List of element data
117
+ """
118
+ pass
119
+
120
+
121
+ class FileFacet(Facet):
122
+ """Base class for file system facets."""
123
+
124
+ @abstractmethod
125
+ async def read(self, path: str) -> bytes:
126
+ """Read file contents.
127
+
128
+ Args:
129
+ path: File path
130
+
131
+ Returns:
132
+ File contents as bytes
133
+ """
134
+ pass
135
+
136
+ @abstractmethod
137
+ async def write(self, path: str, content: bytes) -> None:
138
+ """Write file contents.
139
+
140
+ Args:
141
+ path: File path
142
+ content: Content to write
143
+ """
144
+ pass
145
+
146
+ @abstractmethod
147
+ async def list_dir(self, path: str) -> list[str]:
148
+ """List directory contents.
149
+
150
+ Args:
151
+ path: Directory path
152
+
153
+ Returns:
154
+ List of file/directory names
155
+ """
156
+ pass
157
+
158
+ @abstractmethod
159
+ async def exists(self, path: str) -> bool:
160
+ """Check if file or directory exists.
161
+
162
+ Args:
163
+ path: File or directory path
164
+
165
+ Returns:
166
+ True if exists, False otherwise
167
+ """
168
+ pass
169
+
170
+
171
+ class APIFacet(Facet):
172
+ """Base class for API facets."""
173
+
174
+ @abstractmethod
175
+ async def get(self, path: str, params: Optional[Dict[str, Any]] = None) -> Any:
176
+ """Make a GET request.
177
+
178
+ Args:
179
+ path: API endpoint path
180
+ params: Query parameters
181
+
182
+ Returns:
183
+ API response
184
+ """
185
+ pass
186
+
187
+ @abstractmethod
188
+ async def post(self, path: str, data: Optional[Dict[str, Any]] = None) -> Any:
189
+ """Make a POST request.
190
+
191
+ Args:
192
+ path: API endpoint path
193
+ data: Request data
194
+
195
+ Returns:
196
+ API response
197
+ """
198
+ pass
199
+
200
+ @abstractmethod
201
+ async def put(self, path: str, data: Optional[Dict[str, Any]] = None) -> Any:
202
+ """Make a PUT request.
203
+
204
+ Args:
205
+ path: API endpoint path
206
+ data: Request data
207
+
208
+ Returns:
209
+ API response
210
+ """
211
+ pass
212
+
213
+ @abstractmethod
214
+ async def delete(self, path: str) -> Any:
215
+ """Make a DELETE request.
216
+
217
+ Args:
218
+ path: API endpoint path
219
+
220
+ Returns:
221
+ API response
222
+ """
223
+ pass
@@ -0,0 +1,29 @@
1
+ """Fleet SDK Facet Factory."""
2
+
3
+ from typing import TYPE_CHECKING
4
+ from urllib.parse import urlparse
5
+
6
+ from .base import Facet
7
+ from ..exceptions import FleetFacetError
8
+
9
+ if TYPE_CHECKING:
10
+ from ..env.base import Environment
11
+
12
+
13
+ def create_facet(uri: str, environment: "Environment") -> Facet:
14
+ """Create a facet based on the URI scheme.
15
+
16
+ Args:
17
+ uri: URI identifying the facet (e.g., 'sqlite://crm', 'browser://dom')
18
+ environment: Environment instance
19
+
20
+ Returns:
21
+ Facet instance
22
+
23
+ Raises:
24
+ NotImplementedError: Facet implementations are not yet available
25
+ """
26
+ parsed = urlparse(uri)
27
+ scheme = parsed.scheme.lower()
28
+
29
+ raise NotImplementedError(f"Facet implementation for scheme '{scheme}' not yet available")
@@ -0,0 +1,177 @@
1
+ # Copyright 2025 Fleet AI
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """Fleet Manager API Client for per-instance environment management."""
16
+
17
+ import asyncio
18
+ import logging
19
+ from typing import Any, Dict, Optional
20
+ import aiohttp
21
+ from pydantic import BaseModel, Field
22
+
23
+ from .exceptions import (
24
+ FleetAPIError,
25
+ FleetTimeoutError,
26
+ FleetError,
27
+ )
28
+
29
+ logger = logging.getLogger(__name__)
30
+
31
+
32
+ class ManagerHealthResponse(BaseModel):
33
+ """Response model for manager health checks."""
34
+
35
+ status: str = Field(..., description="Health status")
36
+ timestamp: str = Field(..., description="Timestamp")
37
+ service: str = Field(..., description="Service name")
38
+
39
+
40
+ class TimestampResponse(BaseModel):
41
+ """Response model for timestamp endpoint."""
42
+
43
+ timestamp: str = Field(..., description="Current timestamp")
44
+
45
+
46
+ class FleetManagerClient:
47
+ """Client for interacting with Fleet Manager APIs on individual instances."""
48
+
49
+ def __init__(self, base_url: str):
50
+ """Initialize the manager client.
51
+
52
+ Args:
53
+ base_url: Base URL for the manager API (e.g., https://instanceid.fleetai.com)
54
+ """
55
+ self._base_url = base_url.rstrip('/')
56
+ self._session: Optional[aiohttp.ClientSession] = None
57
+
58
+ async def __aenter__(self):
59
+ """Async context manager entry."""
60
+ await self._ensure_session()
61
+ return self
62
+
63
+ async def __aexit__(self, exc_type, exc_val, exc_tb):
64
+ """Async context manager exit."""
65
+ await self.close()
66
+
67
+ async def _ensure_session(self):
68
+ """Ensure HTTP session is created."""
69
+ if self._session is None or self._session.closed:
70
+ timeout = aiohttp.ClientTimeout(total=30)
71
+ self._session = aiohttp.ClientSession(
72
+ timeout=timeout,
73
+ connector=aiohttp.TCPConnector(limit=10),
74
+ )
75
+
76
+ async def close(self):
77
+ """Close the HTTP session."""
78
+ if self._session and not self._session.closed:
79
+ await self._session.close()
80
+ self._session = None
81
+
82
+ async def _request(
83
+ self,
84
+ method: str,
85
+ path: str,
86
+ data: Optional[Dict[str, Any]] = None,
87
+ params: Optional[Dict[str, Any]] = None,
88
+ headers: Optional[Dict[str, str]] = None,
89
+ timeout: Optional[float] = None,
90
+ ) -> Dict[str, Any]:
91
+ """Make an HTTP request to the Manager API.
92
+
93
+ Args:
94
+ method: HTTP method (GET, POST, etc.)
95
+ path: API endpoint path
96
+ data: Request body data
97
+ params: Query parameters
98
+ headers: Additional headers
99
+ timeout: Request timeout in seconds
100
+
101
+ Returns:
102
+ Response data as dictionary
103
+
104
+ Raises:
105
+ FleetAPIError: If the API returns an error
106
+ FleetTimeoutError: If request times out
107
+ """
108
+ await self._ensure_session()
109
+
110
+ url = f"{self._base_url}{path}"
111
+ request_headers = headers or {}
112
+
113
+ try:
114
+ logger.debug(f"Making {method} request to {url}")
115
+
116
+ async with self._session.request(
117
+ method=method,
118
+ url=url,
119
+ json=data,
120
+ params=params,
121
+ headers=request_headers,
122
+ timeout=aiohttp.ClientTimeout(total=timeout or 30),
123
+ ) as response:
124
+ response_data = await response.json() if response.content_type == "application/json" else {}
125
+
126
+ if response.status == 200:
127
+ logger.debug(f"Manager API request successful: {response.status}")
128
+ return response_data
129
+ else:
130
+ error_message = response_data.get("detail", f"Manager API request failed with status {response.status}")
131
+ raise FleetAPIError(
132
+ error_message,
133
+ status_code=response.status,
134
+ response_data=response_data,
135
+ )
136
+
137
+ except asyncio.TimeoutError:
138
+ raise FleetTimeoutError(f"Request to {url} timed out")
139
+
140
+ except aiohttp.ClientError as e:
141
+ raise FleetAPIError(f"HTTP client error: {e}")
142
+
143
+ # Health check operations
144
+ async def health_check(self) -> ManagerHealthResponse:
145
+ """Check the health of the manager API.
146
+
147
+ Returns:
148
+ ManagerHealthResponse object
149
+ """
150
+ response = await self._request("GET", "/health")
151
+ return ManagerHealthResponse(**response)
152
+
153
+ async def get_timestamp(self) -> TimestampResponse:
154
+ """Get current timestamp from the manager.
155
+
156
+ Returns:
157
+ TimestampResponse object
158
+ """
159
+ response = await self._request("GET", "/timestamp")
160
+ return TimestampResponse(**response)
161
+
162
+ async def test_path(self) -> Dict[str, Any]:
163
+ """Test endpoint to verify path configuration.
164
+
165
+ Returns:
166
+ Test response data
167
+ """
168
+ response = await self._request("GET", "/test-path")
169
+ return response
170
+
171
+ # Future endpoints can be added here as needed:
172
+ # - log_action()
173
+ # - reset_database()
174
+ # - create_snapshots()
175
+ # - generate_diff()
176
+ # - execute_verifier_function()
177
+ # etc.
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: fleet-python
3
+ Version: 0.1.0
4
+ Summary: Python SDK for Fleet environments
5
+ Author-email: Fleet AI <nic@fleet.so>
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://fleetai.com
8
+ Project-URL: Documentation, https://docs.fleetai.com
9
+ Project-URL: Repository, https://github.com/fleet-ai/fleet-sdk
10
+ Project-URL: Issues, https://github.com/fleet-ai/fleet-sdk/issues
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: Apache Software License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.8
16
+ Classifier: Programming Language :: Python :: 3.9
17
+ Classifier: Programming Language :: Python :: 3.10
18
+ Classifier: Programming Language :: Python :: 3.11
19
+ Classifier: Programming Language :: Python :: 3.12
20
+ Requires-Python: >=3.8
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: aiohttp>=3.8.0
24
+ Requires-Dist: pydantic>=2.0.0
25
+ Requires-Dist: typing-extensions>=4.0.0
26
+ Provides-Extra: dev
27
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
28
+ Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
29
+ Requires-Dist: black>=22.0.0; extra == "dev"
30
+ Requires-Dist: isort>=5.0.0; extra == "dev"
31
+ Requires-Dist: mypy>=1.0.0; extra == "dev"
32
+ Requires-Dist: ruff>=0.1.0; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ # Fleet SDK
36
+
37
+ The Fleet Python SDK provides programmatic access to Fleet's environment infrastructure.
38
+
39
+ ## Installation
40
+
41
+ Install the Fleet SDK using pip:
42
+
43
+ ```bash
44
+ pip install fleet-python
45
+ ```
46
+
47
+ ## API Key Setup
48
+
49
+ Fleet requires an API key for authentication. You can obtain one from the [Fleet Platform](https://fleetai.com/dashboard/api-keys).
50
+
51
+ Set your API key as an environment variable:
52
+
53
+ ```bash
54
+ export FLEET_API_KEY="sk_your_key_here"
55
+ ```
56
+
57
+ ## Basic Usage
58
+
59
+ ```python
60
+ import fleet as flt
61
+
62
+ # Create environment by key
63
+ env = await flt.env.make("fira")
64
+
65
+ # Reset environment with seed and options
66
+ await env.reset(
67
+ seed=42,
68
+ timestamp=datetime.now()
69
+ )
70
+
71
+ # Access environment state ('crm' is the resource id for a sqlite database)
72
+ sql = env.state("sqlite://crm")
73
+ await sql.exec("UPDATE customers SET status = 'active' WHERE id = 123")
74
+
75
+ # Clean up
76
+ await env.close()
77
+ ```
78
+
79
+ ## Environment Management
80
+
81
+ ### Creating Instances
82
+
83
+ ```python
84
+ # Create environment instance with explicit version
85
+ env = await flt.env.make("fira:v1.2.5")
86
+
87
+ # Create environment instance with default (latest) version
88
+ env = await flt.env.make("fira")
89
+
90
+ ```
91
+
92
+ ### Connecting to Existing Instances
93
+
94
+ ```python
95
+ # Connect to a running instance
96
+ env = await flt.env.get("env_instance_id")
97
+
98
+ # List all running instances
99
+ instances = await flt.env.list_instances()
100
+ for instance in instances:
101
+ print(f"Instance: {instance.instance_id}")
102
+ print(f"Type: {instance.environment_type}")
103
+ print(f"Status: {instance.status}")
104
+
105
+ # Filter instances by status (running, pending, stopped, error)
106
+ running_instances = await flt.env.list_instances(status_filter="running")
107
+
108
+ # List available environment types
109
+ available_envs = await flt.env.list_envs()
110
+ ```
@@ -0,0 +1,17 @@
1
+ examples/quickstart.py,sha256=AnLlLQYRfrP7y2J69d1HZRlZsjJT-KeBu897MoNfqYM,4671
2
+ fleet/__init__.py,sha256=WhanDP1kC_K84nrEe9ER1iyODW2344wghQ8Rgdi-JmA,1441
3
+ fleet/client.py,sha256=nBjUV901oyR5uE0WAwHEE-xsgyntesGRiLVhVWf793o,12071
4
+ fleet/config.py,sha256=xlKv-gr38oBcyltQiKjgLzxV-JSd3b4x5oxevOjioME,3768
5
+ fleet/exceptions.py,sha256=yG3QWprCw1OnF-vdFBFJWE4m3ftBLBng31Dr__VbjI4,2249
6
+ fleet/manager_client.py,sha256=o01MuTAoeXssI23SSIwsAHM8ck1G6VsOluD_emcpFnE,5900
7
+ fleet/env/__init__.py,sha256=yKJ-35vAVU4VWWkZMDFeElF9uMzsahBm2K-IumOkciU,570
8
+ fleet/env/base.py,sha256=Ry-1t2cyn2EMlpEtO3XJ89FzK7Q471tsZ8WaluIacK8,12046
9
+ fleet/env/factory.py,sha256=BH6ILmc5WvyNanqSf6RqZXR6SgixMoDw5tOnR0PqVhM,13920
10
+ fleet/facets/__init__.py,sha256=oXU7Tkcx1ETlto4mLUwrjj5-5EcsM-lqAqksJMpmDkE,84
11
+ fleet/facets/base.py,sha256=mJAnRiboGWRn5eSjKM6QZTOPDGDiMrl2kwJbONZ3yq0,5400
12
+ fleet/facets/factory.py,sha256=7OecnXJUafsM9mJcNPHXV5djUOUNg8I6p12sfXGCb4g,782
13
+ fleet_python-0.1.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
14
+ fleet_python-0.1.0.dist-info/METADATA,sha256=I6IGquclMRBeM6IVAKs_uQY-8rC7McfSrE850zCcHfU,3040
15
+ fleet_python-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
16
+ fleet_python-0.1.0.dist-info/top_level.txt,sha256=AOyXOrBXUjPcH4BumElz_D95kiWKNIpUbUPFP_9gCLk,15
17
+ fleet_python-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+