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/env/factory.py ADDED
@@ -0,0 +1,446 @@
1
+ """Fleet SDK Environment Factory."""
2
+
3
+ import os
4
+ from typing import Dict, Optional, Any, List, Union, Tuple
5
+ from datetime import datetime
6
+
7
+ from .base import Environment, EnvironmentConfig, RemoteEnvironment
8
+ from ..exceptions import FleetEnvironmentError, FleetAuthenticationError
9
+ from ..config import get_config, FleetConfig
10
+ from ..client import FleetAPIClient, EnvDetails, InstanceRequest
11
+
12
+
13
+ # Registry of available environment types with versioning
14
+ ENVIRONMENT_REGISTRY: Dict[str, Dict[str, Dict[str, str]]] = {
15
+ "browser": {
16
+ "chrome-desktop": {
17
+ "v1": "web_browser",
18
+ "v2": "web_browser",
19
+ "latest": "v2"
20
+ },
21
+ "firefox-desktop": {
22
+ "v1": "web_browser",
23
+ "latest": "v1"
24
+ },
25
+ "safari-desktop": {
26
+ "v1": "web_browser",
27
+ "latest": "v1"
28
+ },
29
+ "chrome-mobile": {
30
+ "v1": "mobile_browser",
31
+ "latest": "v1"
32
+ }
33
+ },
34
+ "database": {
35
+ "postgres": {
36
+ "v1": "database",
37
+ "latest": "v1"
38
+ },
39
+ "mysql": {
40
+ "v1": "database",
41
+ "latest": "v1"
42
+ }
43
+ },
44
+ "file-system": {
45
+ "unix": {
46
+ "v1": "file_system",
47
+ "latest": "v1"
48
+ }
49
+ },
50
+ "api": {
51
+ "rest": {
52
+ "v1": "api",
53
+ "latest": "v1"
54
+ }
55
+ }
56
+ }
57
+
58
+
59
+ class EnvironmentInstance:
60
+ """Represents a live environment instance."""
61
+
62
+ def __init__(
63
+ self,
64
+ instance_id: str,
65
+ env_key: str,
66
+ status: str,
67
+ created_at: str,
68
+ metadata: Optional[Dict[str, Any]] = None
69
+ ):
70
+ self.instance_id = instance_id
71
+ self.env_key = env_key
72
+ self.status = status
73
+ self.created_at = created_at
74
+ self.metadata = metadata or {}
75
+
76
+ def to_dict(self) -> Dict[str, Any]:
77
+ """Convert instance to dictionary representation."""
78
+ return {
79
+ "instance_id": self.instance_id,
80
+ "env_key": self.env_key,
81
+ "status": self.status,
82
+ "created_at": self.created_at,
83
+ "metadata": self.metadata,
84
+ }
85
+
86
+
87
+ def _parse_environment_spec(environment_spec: str) -> Tuple[str, Optional[str]]:
88
+ """Parse environment specification into name and version.
89
+
90
+ Args:
91
+ environment_spec: Environment specification in format:
92
+ - "name:version" (e.g., "fira:v1.2.5")
93
+ - "name" (defaults to None, will use environment's default version)
94
+
95
+ Returns:
96
+ Tuple of (name, version)
97
+
98
+ Raises:
99
+ FleetEnvironmentError: If the specification format is invalid
100
+ """
101
+ if ":" in environment_spec:
102
+ name, version = environment_spec.split(":", 1)
103
+ return name, version
104
+ else:
105
+ return environment_spec, None
106
+
107
+
108
+
109
+
110
+
111
+ async def make(
112
+ environment_spec: str,
113
+ version: Optional[str] = None,
114
+ region: Optional[str] = None,
115
+ **kwargs: Any,
116
+ ) -> Environment:
117
+ """Create a Fleet environment.
118
+
119
+ Args:
120
+ environment_spec: Environment specification in format:
121
+ - "name:version" (e.g., "fira:v1.2.5")
122
+ - "name" (e.g., "fira" - uses default version)
123
+ version: Optional version to override any version in environment_spec
124
+ region: Optional AWS region (defaults to "us-west-1")
125
+ **kwargs: Additional configuration options
126
+
127
+ Returns:
128
+ Environment instance
129
+
130
+ Raises:
131
+ FleetEnvironmentError: If environment specification is invalid
132
+ FleetAuthenticationError: If API key is missing or invalid
133
+ FleetConfigurationError: If configuration is invalid
134
+ """
135
+ # Parse the environment specification
136
+ env_name, parsed_version = _parse_environment_spec(environment_spec)
137
+
138
+ # Use explicit version parameter if provided, otherwise use parsed version
139
+ final_version = version or parsed_version
140
+
141
+ # Load configuration from environment variables
142
+ config = get_config(**kwargs)
143
+
144
+ # API key is required
145
+ if not config.api_key:
146
+ raise FleetAuthenticationError(
147
+ "API key is required. Set FLEET_API_KEY environment variable."
148
+ )
149
+
150
+ # Create environment configuration
151
+ env_config = EnvironmentConfig(
152
+ environment_type=env_name,
153
+ api_key=config.api_key,
154
+ base_url=config.base_url,
155
+ metadata=kwargs,
156
+ )
157
+
158
+ # Add version to metadata if specified
159
+ if final_version:
160
+ env_config.metadata["version"] = final_version
161
+
162
+ # Add region to metadata if specified
163
+ if region:
164
+ env_config.metadata["region"] = region
165
+
166
+ # Create API client and create instance
167
+ async with FleetAPIClient(config) as client:
168
+ try:
169
+ # Create instance request
170
+ instance_request = InstanceRequest(
171
+ env_key=env_name,
172
+ version=final_version,
173
+ region=region,
174
+ **kwargs
175
+ )
176
+
177
+ # Create the instance
178
+ instance_response = await client.create_instance(instance_request)
179
+
180
+ # Create environment instance with the created instance
181
+ env = RemoteEnvironment(env_config, instance_response=instance_response)
182
+
183
+ # Initialize environment
184
+ await _initialize_environment(env)
185
+
186
+ return env
187
+
188
+ except Exception as e:
189
+ raise FleetEnvironmentError(f"Failed to create environment instance: {e}")
190
+
191
+
192
+ async def get(
193
+ instance_id: str,
194
+ **kwargs: Any,
195
+ ) -> Environment:
196
+ """Hydrate an environment from one that is already running.
197
+
198
+ Args:
199
+ instance_id: ID of the running environment instance to connect to
200
+ **kwargs: Additional configuration options
201
+
202
+ Returns:
203
+ Environment instance connected to the running environment
204
+
205
+ Raises:
206
+ FleetEnvironmentError: If instance is not found or not accessible
207
+ FleetAuthenticationError: If API key is missing or invalid
208
+ FleetConfigurationError: If configuration is invalid
209
+ """
210
+ # Load configuration from environment variables
211
+ config = get_config(**kwargs)
212
+
213
+ if not config.api_key:
214
+ raise FleetAuthenticationError(
215
+ "API key is required. Set FLEET_API_KEY environment variable."
216
+ )
217
+
218
+ # Create API client
219
+ async with FleetAPIClient(config) as client:
220
+ try:
221
+ # Get instance details from API
222
+ instance_response = await client.get_instance(instance_id)
223
+
224
+ # Create environment configuration based on instance details
225
+ env_config = EnvironmentConfig(
226
+ environment_type=instance_response.env_key,
227
+ api_key=config.api_key,
228
+ base_url=config.base_url,
229
+ metadata=kwargs,
230
+ )
231
+
232
+ # Create environment instance with existing instance ID
233
+ env = RemoteEnvironment(env_config, instance_id=instance_id)
234
+
235
+ # Initialize environment
236
+ await _initialize_environment(env)
237
+
238
+ return env
239
+
240
+ except Exception as e:
241
+ raise FleetEnvironmentError(f"Failed to get environment instance {instance_id}: {e}")
242
+
243
+
244
+ async def list_instances(
245
+ status: Optional[str] = None,
246
+ env_key_filter: Optional[str] = None,
247
+ **kwargs: Any,
248
+ ) -> List[EnvironmentInstance]:
249
+ """Get a directory of all live environment instances.
250
+
251
+ Args:
252
+ status: Filter by instance status (e.g., 'running', 'paused', 'stopped')
253
+ env_key_filter: Filter by environment key (e.g., 'fira', 'dropbox')
254
+ **kwargs: Additional query parameters
255
+
256
+ Returns:
257
+ List of EnvironmentInstance objects representing live instances
258
+
259
+ Raises:
260
+ FleetAuthenticationError: If API key is missing or invalid
261
+ FleetAPIError: If API request fails
262
+ FleetConfigurationError: If configuration is invalid
263
+ """
264
+ # Load configuration from environment variables
265
+ config = get_config(**kwargs)
266
+
267
+ if not config.api_key:
268
+ raise FleetAuthenticationError(
269
+ "API key is required. Set FLEET_API_KEY environment variable."
270
+ )
271
+
272
+ # Create API client
273
+ async with FleetAPIClient(config) as client:
274
+ try:
275
+ # Get all instances from API
276
+ instances = await client.list_instances(status=status)
277
+
278
+ # Convert to EnvironmentInstance objects and apply filters
279
+ result = []
280
+ for instance in instances:
281
+ # Apply environment key filter if specified
282
+ if env_key_filter and instance.env_key != env_key_filter:
283
+ continue
284
+
285
+ env_instance = EnvironmentInstance(
286
+ instance_id=instance.instance_id,
287
+ env_key=instance.env_key,
288
+ status=instance.status,
289
+ created_at=instance.created_at,
290
+ metadata={
291
+ "version": instance.version,
292
+ "region": instance.region,
293
+ "team_id": instance.team_id,
294
+ "urls": instance.urls.model_dump(),
295
+ "health": instance.health,
296
+ }
297
+ )
298
+ result.append(env_instance)
299
+
300
+ return result
301
+
302
+ except Exception as e:
303
+ raise FleetEnvironmentError(f"Failed to list instances: {e}")
304
+
305
+
306
+ async def list_envs(**kwargs: Any) -> List[EnvDetails]:
307
+ """Get list of available environments from Fleet service.
308
+
309
+ Args:
310
+ **kwargs: Additional query parameters
311
+
312
+ Returns:
313
+ List of EnvDetails objects with environment details
314
+
315
+ Raises:
316
+ FleetAuthenticationError: If API key is missing or invalid
317
+ FleetAPIError: If API request fails
318
+ """
319
+ # Load configuration from environment variables
320
+ config = get_config(**kwargs)
321
+
322
+ if not config.api_key:
323
+ raise FleetAuthenticationError(
324
+ "API key is required. Set FLEET_API_KEY environment variable."
325
+ )
326
+
327
+ # Create API client
328
+ async with FleetAPIClient(config) as client:
329
+ # Get available environments from API - no fallback
330
+ environments = await client.list_environments()
331
+ return environments
332
+
333
+
334
+ def list_environments() -> List[str]:
335
+ """List all available environment specifications.
336
+
337
+ Returns:
338
+ List of environment specifications in format "category/name:version"
339
+ """
340
+ env_specs = []
341
+ for category, names in ENVIRONMENT_REGISTRY.items():
342
+ for name, versions in names.items():
343
+ for version in versions.keys():
344
+ if version != "latest": # Don't include "latest" in the list
345
+ env_specs.append(f"{category}/{name}:{version}")
346
+
347
+ return sorted(env_specs)
348
+
349
+
350
+ def list_categories() -> List[str]:
351
+ """List all available environment categories.
352
+
353
+ Returns:
354
+ List of category names
355
+ """
356
+ return list(ENVIRONMENT_REGISTRY.keys())
357
+
358
+
359
+ def list_names(category: str) -> List[str]:
360
+ """List all available environment names in a category.
361
+
362
+ Args:
363
+ category: Environment category
364
+
365
+ Returns:
366
+ List of environment names in the category
367
+
368
+ Raises:
369
+ FleetEnvironmentError: If category is not found
370
+ """
371
+ if category not in ENVIRONMENT_REGISTRY:
372
+ available_categories = ", ".join(ENVIRONMENT_REGISTRY.keys())
373
+ raise FleetEnvironmentError(
374
+ f"Unknown environment category: {category}. "
375
+ f"Available categories: {available_categories}"
376
+ )
377
+
378
+ return list(ENVIRONMENT_REGISTRY[category].keys())
379
+
380
+
381
+ def list_versions(category: str, name: str) -> List[str]:
382
+ """List all available versions for an environment.
383
+
384
+ Args:
385
+ category: Environment category
386
+ name: Environment name
387
+
388
+ Returns:
389
+ List of available versions (excluding "latest")
390
+
391
+ Raises:
392
+ FleetEnvironmentError: If category or name is not found
393
+ """
394
+ if category not in ENVIRONMENT_REGISTRY:
395
+ available_categories = ", ".join(ENVIRONMENT_REGISTRY.keys())
396
+ raise FleetEnvironmentError(
397
+ f"Unknown environment category: {category}. "
398
+ f"Available categories: {available_categories}"
399
+ )
400
+
401
+ if name not in ENVIRONMENT_REGISTRY[category]:
402
+ available_names = ", ".join(ENVIRONMENT_REGISTRY[category].keys())
403
+ raise FleetEnvironmentError(
404
+ f"Unknown environment name: {name} in category {category}. "
405
+ f"Available names: {available_names}"
406
+ )
407
+
408
+ versions = [v for v in ENVIRONMENT_REGISTRY[category][name].keys() if v != "latest"]
409
+ return sorted(versions)
410
+
411
+
412
+ def is_environment_supported(environment_spec: str) -> bool:
413
+ """Check if an environment specification is supported.
414
+
415
+ Args:
416
+ environment_spec: Environment specification to check
417
+
418
+ Returns:
419
+ True if the environment is supported, False otherwise
420
+ """
421
+ try:
422
+ env_name, version = _parse_environment_spec(environment_spec)
423
+ # For now, we'll just check if we can parse it successfully
424
+ # In the future, we could validate against the API
425
+ return True
426
+ except FleetEnvironmentError:
427
+ return False
428
+
429
+
430
+ async def _initialize_environment(env: Environment) -> None:
431
+ """Initialize an environment after creation.
432
+
433
+ Args:
434
+ env: Environment instance to initialize
435
+ """
436
+ # Perform any necessary initialization
437
+ # This could include:
438
+ # - Validating API key
439
+ # - Setting up HTTP client
440
+ # - Registering default facets
441
+ # - Performing health checks
442
+
443
+ pass
444
+
445
+
446
+
fleet/exceptions.py ADDED
@@ -0,0 +1,73 @@
1
+ """Fleet SDK Exception Classes."""
2
+
3
+ from typing import Any, Dict, Optional
4
+
5
+
6
+ class FleetError(Exception):
7
+ """Base exception for all Fleet SDK errors."""
8
+
9
+ def __init__(self, message: str, details: Optional[Dict[str, Any]] = None):
10
+ super().__init__(message)
11
+ self.message = message
12
+ self.details = details or {}
13
+
14
+
15
+ class FleetAPIError(FleetError):
16
+ """Exception raised when Fleet API returns an error."""
17
+
18
+ def __init__(
19
+ self,
20
+ message: str,
21
+ status_code: Optional[int] = None,
22
+ response_data: Optional[Dict[str, Any]] = None,
23
+ details: Optional[Dict[str, Any]] = None,
24
+ ):
25
+ super().__init__(message, details)
26
+ self.status_code = status_code
27
+ self.response_data = response_data or {}
28
+
29
+
30
+ class FleetTimeoutError(FleetError):
31
+ """Exception raised when a Fleet operation times out."""
32
+
33
+ def __init__(self, message: str, timeout_duration: Optional[float] = None):
34
+ super().__init__(message)
35
+ self.timeout_duration = timeout_duration
36
+
37
+
38
+ class FleetAuthenticationError(FleetAPIError):
39
+ """Exception raised when authentication fails."""
40
+
41
+ def __init__(self, message: str = "Authentication failed"):
42
+ super().__init__(message, status_code=401)
43
+
44
+
45
+ class FleetRateLimitError(FleetAPIError):
46
+ """Exception raised when rate limit is exceeded."""
47
+
48
+ def __init__(self, message: str = "Rate limit exceeded"):
49
+ super().__init__(message, status_code=429)
50
+
51
+
52
+ class FleetEnvironmentError(FleetError):
53
+ """Exception raised when environment operations fail."""
54
+
55
+ def __init__(self, message: str, environment_id: Optional[str] = None):
56
+ super().__init__(message)
57
+ self.environment_id = environment_id
58
+
59
+
60
+ class FleetFacetError(FleetError):
61
+ """Exception raised when facet operations fail."""
62
+
63
+ def __init__(self, message: str, facet_type: Optional[str] = None):
64
+ super().__init__(message)
65
+ self.facet_type = facet_type
66
+
67
+
68
+ class FleetConfigurationError(FleetError):
69
+ """Exception raised when configuration is invalid."""
70
+
71
+ def __init__(self, message: str, config_key: Optional[str] = None):
72
+ super().__init__(message)
73
+ self.config_key = config_key
@@ -0,0 +1,7 @@
1
+ """Fleet SDK Facet Module."""
2
+
3
+ from .base import Facet
4
+
5
+ __all__ = [
6
+ "Facet",
7
+ ]