fleet-python 0.2.95__tar.gz → 0.2.96__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 (118) hide show
  1. {fleet_python-0.2.95/fleet_python.egg-info → fleet_python-0.2.96}/PKG-INFO +1 -1
  2. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/__init__.py +1 -1
  3. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/_async/__init__.py +1 -1
  4. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/_async/base.py +1 -1
  5. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/_async/client.py +19 -0
  6. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/_async/instance/client.py +26 -0
  7. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/_async/models.py +1 -0
  8. fleet_python-0.2.96/fleet/_async/resources/api.py +200 -0
  9. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/base.py +1 -1
  10. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/client.py +19 -0
  11. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/instance/client.py +26 -0
  12. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/instance/models.py +1 -0
  13. fleet_python-0.2.96/fleet/resources/api.py +200 -0
  14. {fleet_python-0.2.95 → fleet_python-0.2.96/fleet_python.egg-info}/PKG-INFO +1 -1
  15. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet_python.egg-info/SOURCES.txt +2 -0
  16. {fleet_python-0.2.95 → fleet_python-0.2.96}/pyproject.toml +1 -1
  17. {fleet_python-0.2.95 → fleet_python-0.2.96}/LICENSE +0 -0
  18. {fleet_python-0.2.95 → fleet_python-0.2.96}/README.md +0 -0
  19. {fleet_python-0.2.95 → fleet_python-0.2.96}/examples/diff_example.py +0 -0
  20. {fleet_python-0.2.95 → fleet_python-0.2.96}/examples/dsl_example.py +0 -0
  21. {fleet_python-0.2.95 → fleet_python-0.2.96}/examples/example.py +0 -0
  22. {fleet_python-0.2.95 → fleet_python-0.2.96}/examples/exampleResume.py +0 -0
  23. {fleet_python-0.2.95 → fleet_python-0.2.96}/examples/example_account.py +0 -0
  24. {fleet_python-0.2.95 → fleet_python-0.2.96}/examples/example_action_log.py +0 -0
  25. {fleet_python-0.2.95 → fleet_python-0.2.96}/examples/example_client.py +0 -0
  26. {fleet_python-0.2.95 → fleet_python-0.2.96}/examples/example_mcp_anthropic.py +0 -0
  27. {fleet_python-0.2.95 → fleet_python-0.2.96}/examples/example_mcp_openai.py +0 -0
  28. {fleet_python-0.2.95 → fleet_python-0.2.96}/examples/example_sync.py +0 -0
  29. {fleet_python-0.2.95 → fleet_python-0.2.96}/examples/example_task.py +0 -0
  30. {fleet_python-0.2.95 → fleet_python-0.2.96}/examples/example_tasks.py +0 -0
  31. {fleet_python-0.2.95 → fleet_python-0.2.96}/examples/example_verifier.py +0 -0
  32. {fleet_python-0.2.95 → fleet_python-0.2.96}/examples/export_tasks.py +0 -0
  33. {fleet_python-0.2.95 → fleet_python-0.2.96}/examples/fetch_tasks.py +0 -0
  34. {fleet_python-0.2.95 → fleet_python-0.2.96}/examples/gemini_example.py +0 -0
  35. {fleet_python-0.2.95 → fleet_python-0.2.96}/examples/import_tasks.py +0 -0
  36. {fleet_python-0.2.95 → fleet_python-0.2.96}/examples/iterate_verifiers.py +0 -0
  37. {fleet_python-0.2.95 → fleet_python-0.2.96}/examples/json_tasks_example.py +0 -0
  38. {fleet_python-0.2.95 → fleet_python-0.2.96}/examples/nova_act_example.py +0 -0
  39. {fleet_python-0.2.95 → fleet_python-0.2.96}/examples/openai_example.py +0 -0
  40. {fleet_python-0.2.95 → fleet_python-0.2.96}/examples/openai_simple_example.py +0 -0
  41. {fleet_python-0.2.95 → fleet_python-0.2.96}/examples/query_builder_example.py +0 -0
  42. {fleet_python-0.2.95 → fleet_python-0.2.96}/examples/quickstart.py +0 -0
  43. {fleet_python-0.2.95 → fleet_python-0.2.96}/examples/test_cdp_logging.py +0 -0
  44. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/_async/env/__init__.py +0 -0
  45. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/_async/env/client.py +0 -0
  46. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/_async/exceptions.py +0 -0
  47. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/_async/global_client.py +0 -0
  48. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/_async/instance/__init__.py +0 -0
  49. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/_async/instance/base.py +0 -0
  50. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/_async/resources/__init__.py +0 -0
  51. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/_async/resources/base.py +0 -0
  52. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/_async/resources/browser.py +0 -0
  53. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/_async/resources/mcp.py +0 -0
  54. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/_async/resources/sqlite.py +0 -0
  55. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/_async/tasks.py +0 -0
  56. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/_async/verifiers/__init__.py +0 -0
  57. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/_async/verifiers/bundler.py +0 -0
  58. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/_async/verifiers/verifier.py +0 -0
  59. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/agent/__init__.py +0 -0
  60. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/agent/gemini_cua/Dockerfile +0 -0
  61. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/agent/gemini_cua/__init__.py +0 -0
  62. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/agent/gemini_cua/agent.py +0 -0
  63. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/agent/gemini_cua/mcp/main.py +0 -0
  64. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/agent/gemini_cua/mcp_server/__init__.py +0 -0
  65. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/agent/gemini_cua/mcp_server/main.py +0 -0
  66. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/agent/gemini_cua/mcp_server/tools.py +0 -0
  67. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/agent/gemini_cua/requirements.txt +0 -0
  68. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/agent/gemini_cua/start.sh +0 -0
  69. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/agent/orchestrator.py +0 -0
  70. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/agent/types.py +0 -0
  71. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/agent/utils.py +0 -0
  72. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/cli.py +0 -0
  73. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/config.py +0 -0
  74. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/env/__init__.py +0 -0
  75. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/env/client.py +0 -0
  76. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/eval/__init__.py +0 -0
  77. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/eval/uploader.py +0 -0
  78. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/exceptions.py +0 -0
  79. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/global_client.py +0 -0
  80. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/instance/__init__.py +0 -0
  81. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/instance/base.py +0 -0
  82. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/models.py +0 -0
  83. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/proxy/__init__.py +0 -0
  84. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/proxy/proxy.py +0 -0
  85. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/proxy/whitelist.py +0 -0
  86. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/resources/__init__.py +0 -0
  87. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/resources/base.py +0 -0
  88. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/resources/browser.py +0 -0
  89. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/resources/mcp.py +0 -0
  90. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/resources/sqlite.py +0 -0
  91. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/tasks.py +0 -0
  92. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/types.py +0 -0
  93. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/utils/__init__.py +0 -0
  94. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/utils/http_logging.py +0 -0
  95. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/utils/logging.py +0 -0
  96. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/utils/playwright.py +0 -0
  97. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/verifiers/__init__.py +0 -0
  98. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/verifiers/bundler.py +0 -0
  99. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/verifiers/code.py +0 -0
  100. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/verifiers/db.py +0 -0
  101. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/verifiers/decorator.py +0 -0
  102. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/verifiers/parse.py +0 -0
  103. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/verifiers/sql_differ.py +0 -0
  104. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet/verifiers/verifier.py +0 -0
  105. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet_python.egg-info/dependency_links.txt +0 -0
  106. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet_python.egg-info/entry_points.txt +0 -0
  107. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet_python.egg-info/requires.txt +0 -0
  108. {fleet_python-0.2.95 → fleet_python-0.2.96}/fleet_python.egg-info/top_level.txt +0 -0
  109. {fleet_python-0.2.95 → fleet_python-0.2.96}/scripts/fix_sync_imports.py +0 -0
  110. {fleet_python-0.2.95 → fleet_python-0.2.96}/scripts/unasync.py +0 -0
  111. {fleet_python-0.2.95 → fleet_python-0.2.96}/setup.cfg +0 -0
  112. {fleet_python-0.2.95 → fleet_python-0.2.96}/tests/__init__.py +0 -0
  113. {fleet_python-0.2.95 → fleet_python-0.2.96}/tests/test_app_method.py +0 -0
  114. {fleet_python-0.2.95 → fleet_python-0.2.96}/tests/test_expect_only.py +0 -0
  115. {fleet_python-0.2.95 → fleet_python-0.2.96}/tests/test_instance_dispatch.py +0 -0
  116. {fleet_python-0.2.95 → fleet_python-0.2.96}/tests/test_sqlite_resource_dual_mode.py +0 -0
  117. {fleet_python-0.2.95 → fleet_python-0.2.96}/tests/test_sqlite_shared_memory_behavior.py +0 -0
  118. {fleet_python-0.2.95 → fleet_python-0.2.96}/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.95
3
+ Version: 0.2.96
4
4
  Summary: Python SDK for Fleet environments
5
5
  Author-email: Fleet AI <nic@fleet.so>
6
6
  License: Apache-2.0
@@ -73,7 +73,7 @@ from . import env
73
73
  from . import global_client as _global_client
74
74
  from ._async import global_client as _async_global_client
75
75
 
76
- __version__ = "0.2.95"
76
+ __version__ = "0.2.96"
77
77
 
78
78
  __all__ = [
79
79
  # Core classes
@@ -44,7 +44,7 @@ from ..types import VerifierFunction
44
44
  from .. import env
45
45
  from . import global_client as _async_global_client
46
46
 
47
- __version__ = "0.2.95"
47
+ __version__ = "0.2.96"
48
48
 
49
49
  __all__ = [
50
50
  # Core classes
@@ -26,7 +26,7 @@ from .exceptions import (
26
26
  try:
27
27
  from .. import __version__
28
28
  except ImportError:
29
- __version__ = "0.2.95"
29
+ __version__ = "0.2.96"
30
30
 
31
31
  logger = logging.getLogger(__name__)
32
32
 
@@ -172,6 +172,7 @@ from .resources.base import Resource
172
172
  from .resources.sqlite import AsyncSQLiteResource
173
173
  from .resources.browser import AsyncBrowserResource
174
174
  from .resources.mcp import AsyncMCPResource
175
+ from .resources.api import AsyncAPIResource
175
176
 
176
177
  logger = logging.getLogger(__name__)
177
178
 
@@ -385,6 +386,24 @@ class AsyncEnv(EnvironmentBase):
385
386
  def browser(self, name: str = "cdp") -> AsyncBrowserResource:
386
387
  return self.instance.browser(name)
387
388
 
389
+ def api(self, name: str = "api") -> AsyncAPIResource:
390
+ """Get an API resource for making HTTP requests to the app's API.
391
+
392
+ Args:
393
+ name: Name for the API resource (default: "api")
394
+
395
+ Returns:
396
+ AsyncAPIResource for making HTTP requests
397
+ """
398
+ # Use urls.api if available, otherwise fall back to urls.root + "/raw"
399
+ if self.urls and self.urls.api:
400
+ base_url = self.urls.api
401
+ elif self.urls and self.urls.root:
402
+ base_url = f"{self.urls.root.rstrip('/')}/raw"
403
+ else:
404
+ raise ValueError("No API URL configured for this environment")
405
+ return self.instance.api(name, base_url)
406
+
388
407
  @property
389
408
  def mcp(self) -> AsyncMCPResource:
390
409
  mcp_url = f"{self.urls.root}mcp"
@@ -9,6 +9,7 @@ from urllib.parse import urlparse
9
9
 
10
10
  from ..resources.sqlite import AsyncSQLiteResource
11
11
  from ..resources.browser import AsyncBrowserResource
12
+ from ..resources.api import AsyncAPIResource
12
13
  from ..resources.base import Resource
13
14
 
14
15
  from fleet.verifiers import DatabaseSnapshot
@@ -23,6 +24,7 @@ from ...instance.models import (
23
24
  ResetResponse,
24
25
  Resource as ResourceModel,
25
26
  ResourceType,
27
+ ResourceMode,
26
28
  HealthResponse,
27
29
  ExecuteFunctionRequest,
28
30
  ExecuteFunctionResponse,
@@ -35,6 +37,7 @@ logger = logging.getLogger(__name__)
35
37
  RESOURCE_TYPES = {
36
38
  ResourceType.db: AsyncSQLiteResource,
37
39
  ResourceType.cdp: AsyncBrowserResource,
40
+ ResourceType.api: AsyncAPIResource,
38
41
  }
39
42
 
40
43
  ValidatorType = Callable[
@@ -102,6 +105,29 @@ class AsyncInstanceClient:
102
105
  self._resources_state[ResourceType.cdp.value][name], self.client
103
106
  )
104
107
 
108
+ def api(self, name: str, base_url: str) -> AsyncAPIResource:
109
+ """
110
+ Returns an API resource for making HTTP requests.
111
+
112
+ Args:
113
+ name: The name of the API resource
114
+ base_url: The base URL for API requests
115
+
116
+ Returns:
117
+ An AsyncAPIResource for making HTTP requests
118
+ """
119
+ # Create a minimal resource model for API
120
+ resource_model = ResourceModel(
121
+ name=name,
122
+ type=ResourceType.api,
123
+ mode=ResourceMode.rw,
124
+ )
125
+ return AsyncAPIResource(
126
+ resource_model,
127
+ base_url=base_url,
128
+ client=self.client.httpx_client if self.client else None,
129
+ )
130
+
105
131
  async def resources(self) -> List[Resource]:
106
132
  await self._load_resources()
107
133
  return [
@@ -120,6 +120,7 @@ class ResourceMode(Enum):
120
120
  class ResourceType(Enum):
121
121
  sqlite = "sqlite"
122
122
  cdp = "cdp"
123
+ api = "api"
123
124
 
124
125
 
125
126
  class RestoreRequest(BaseModel):
@@ -0,0 +1,200 @@
1
+ """Async API Resource for making HTTP requests to the app's API endpoint."""
2
+
3
+ from typing import Any, Dict, Optional
4
+ import httpx
5
+
6
+ from .base import Resource
7
+ from ...instance.models import Resource as ResourceModel
8
+
9
+
10
+ class AsyncAPIResponse:
11
+ """Simple wrapper around httpx.Response with a requests-like interface."""
12
+
13
+ def __init__(self, response: httpx.Response):
14
+ self._response = response
15
+ self.status_code: int = response.status_code
16
+ self.headers: httpx.Headers = response.headers
17
+ self.text: str = response.text
18
+ self.content: bytes = response.content
19
+ self.url: str = str(response.url)
20
+ self.ok: bool = response.is_success
21
+
22
+ def json(self) -> Any:
23
+ """Parse response body as JSON."""
24
+ return self._response.json()
25
+
26
+ def raise_for_status(self) -> "AsyncAPIResponse":
27
+ """Raise an HTTPStatusError if the response has an error status code."""
28
+ self._response.raise_for_status()
29
+ return self
30
+
31
+ def __repr__(self) -> str:
32
+ return f"<AsyncAPIResponse [{self.status_code}]>"
33
+
34
+
35
+ class AsyncAPIResource(Resource):
36
+ """Async HTTP client for making requests to the app's API endpoint.
37
+
38
+ Provides a requests-like interface for interacting with the app's REST API.
39
+
40
+ Example:
41
+ api = env.api()
42
+ response = await api.get("/users/1")
43
+ print(response.status_code) # 200
44
+ print(response.json()) # {"id": 1, "name": "John"}
45
+
46
+ # With headers/auth
47
+ response = await api.post(
48
+ "/users",
49
+ json={"name": "Jane"},
50
+ headers={"Authorization": "Bearer xxx"}
51
+ )
52
+ """
53
+
54
+ def __init__(
55
+ self,
56
+ resource: ResourceModel,
57
+ base_url: str,
58
+ client: Optional[httpx.AsyncClient] = None,
59
+ ):
60
+ super().__init__(resource)
61
+ self.base_url = base_url.rstrip("/")
62
+ self._client = client or httpx.AsyncClient()
63
+
64
+ def _build_url(self, path: str) -> str:
65
+ """Build full URL from base_url and path."""
66
+ if path.startswith("/"):
67
+ return f"{self.base_url}{path}"
68
+ return f"{self.base_url}/{path}"
69
+
70
+ async def request(
71
+ self,
72
+ method: str,
73
+ path: str,
74
+ *,
75
+ params: Optional[Dict[str, Any]] = None,
76
+ json: Optional[Any] = None,
77
+ data: Optional[Dict[str, Any]] = None,
78
+ headers: Optional[Dict[str, str]] = None,
79
+ cookies: Optional[Dict[str, str]] = None,
80
+ timeout: Optional[float] = None,
81
+ **kwargs: Any,
82
+ ) -> AsyncAPIResponse:
83
+ """Make an HTTP request.
84
+
85
+ Args:
86
+ method: HTTP method (GET, POST, PUT, PATCH, DELETE, etc.)
87
+ path: URL path (relative to base_url)
88
+ params: Query parameters
89
+ json: JSON body (will be serialized)
90
+ data: Form data
91
+ headers: Request headers
92
+ cookies: Request cookies
93
+ timeout: Request timeout in seconds
94
+ **kwargs: Additional arguments passed to httpx
95
+
96
+ Returns:
97
+ AsyncAPIResponse with status_code, headers, text, content, json() method
98
+ """
99
+ url = self._build_url(path)
100
+ response = await self._client.request(
101
+ method,
102
+ url,
103
+ params=params,
104
+ json=json,
105
+ data=data,
106
+ headers=headers,
107
+ cookies=cookies,
108
+ timeout=timeout,
109
+ **kwargs,
110
+ )
111
+ return AsyncAPIResponse(response)
112
+
113
+ async def get(
114
+ self,
115
+ path: str,
116
+ *,
117
+ params: Optional[Dict[str, Any]] = None,
118
+ headers: Optional[Dict[str, str]] = None,
119
+ **kwargs: Any,
120
+ ) -> AsyncAPIResponse:
121
+ """Make a GET request."""
122
+ return await self.request("GET", path, params=params, headers=headers, **kwargs)
123
+
124
+ async def post(
125
+ self,
126
+ path: str,
127
+ *,
128
+ json: Optional[Any] = None,
129
+ data: Optional[Dict[str, Any]] = None,
130
+ params: Optional[Dict[str, Any]] = None,
131
+ headers: Optional[Dict[str, str]] = None,
132
+ **kwargs: Any,
133
+ ) -> AsyncAPIResponse:
134
+ """Make a POST request."""
135
+ return await self.request(
136
+ "POST", path, json=json, data=data, params=params, headers=headers, **kwargs
137
+ )
138
+
139
+ async def put(
140
+ self,
141
+ path: str,
142
+ *,
143
+ json: Optional[Any] = None,
144
+ data: Optional[Dict[str, Any]] = None,
145
+ params: Optional[Dict[str, Any]] = None,
146
+ headers: Optional[Dict[str, str]] = None,
147
+ **kwargs: Any,
148
+ ) -> AsyncAPIResponse:
149
+ """Make a PUT request."""
150
+ return await self.request(
151
+ "PUT", path, json=json, data=data, params=params, headers=headers, **kwargs
152
+ )
153
+
154
+ async def patch(
155
+ self,
156
+ path: str,
157
+ *,
158
+ json: Optional[Any] = None,
159
+ data: Optional[Dict[str, Any]] = None,
160
+ params: Optional[Dict[str, Any]] = None,
161
+ headers: Optional[Dict[str, str]] = None,
162
+ **kwargs: Any,
163
+ ) -> AsyncAPIResponse:
164
+ """Make a PATCH request."""
165
+ return await self.request(
166
+ "PATCH", path, json=json, data=data, params=params, headers=headers, **kwargs
167
+ )
168
+
169
+ async def delete(
170
+ self,
171
+ path: str,
172
+ *,
173
+ params: Optional[Dict[str, Any]] = None,
174
+ headers: Optional[Dict[str, str]] = None,
175
+ **kwargs: Any,
176
+ ) -> AsyncAPIResponse:
177
+ """Make a DELETE request."""
178
+ return await self.request("DELETE", path, params=params, headers=headers, **kwargs)
179
+
180
+ async def head(
181
+ self,
182
+ path: str,
183
+ *,
184
+ params: Optional[Dict[str, Any]] = None,
185
+ headers: Optional[Dict[str, str]] = None,
186
+ **kwargs: Any,
187
+ ) -> AsyncAPIResponse:
188
+ """Make a HEAD request."""
189
+ return await self.request("HEAD", path, params=params, headers=headers, **kwargs)
190
+
191
+ async def options(
192
+ self,
193
+ path: str,
194
+ *,
195
+ params: Optional[Dict[str, Any]] = None,
196
+ headers: Optional[Dict[str, str]] = None,
197
+ **kwargs: Any,
198
+ ) -> AsyncAPIResponse:
199
+ """Make an OPTIONS request."""
200
+ return await self.request("OPTIONS", path, params=params, headers=headers, **kwargs)
@@ -27,7 +27,7 @@ from .exceptions import (
27
27
  try:
28
28
  from . import __version__
29
29
  except ImportError:
30
- __version__ = "0.2.95"
30
+ __version__ = "0.2.96"
31
31
 
32
32
  logger = logging.getLogger(__name__)
33
33
 
@@ -178,6 +178,7 @@ from .resources.base import Resource
178
178
  from .resources.sqlite import SQLiteResource
179
179
  from .resources.browser import BrowserResource
180
180
  from .resources.mcp import SyncMCPResource
181
+ from .resources.api import APIResource
181
182
 
182
183
  logger = logging.getLogger(__name__)
183
184
 
@@ -397,6 +398,24 @@ class SyncEnv(EnvironmentBase):
397
398
  def browser(self, name: str = "cdp") -> BrowserResource:
398
399
  return self.instance.browser(name)
399
400
 
401
+ def api(self, name: str = "api") -> APIResource:
402
+ """Get an API resource for making HTTP requests to the app's API.
403
+
404
+ Args:
405
+ name: Name for the API resource (default: "api")
406
+
407
+ Returns:
408
+ APIResource for making HTTP requests
409
+ """
410
+ # Use urls.api if available, otherwise fall back to urls.root + "/raw"
411
+ if self.urls and self.urls.api:
412
+ base_url = self.urls.api
413
+ elif self.urls and self.urls.root:
414
+ base_url = f"{self.urls.root.rstrip('/')}/raw"
415
+ else:
416
+ raise ValueError("No API URL configured for this environment")
417
+ return self.instance.api(name, base_url)
418
+
400
419
  @property
401
420
  def mcp(self) -> SyncMCPResource:
402
421
  mcp_url = f"{self.urls.root}mcp"
@@ -9,6 +9,7 @@ from urllib.parse import urlparse
9
9
 
10
10
  from ..resources.sqlite import SQLiteResource
11
11
  from ..resources.browser import BrowserResource
12
+ from ..resources.api import APIResource
12
13
  from ..resources.base import Resource
13
14
 
14
15
  from fleet.verifiers import DatabaseSnapshot
@@ -23,6 +24,7 @@ from .models import (
23
24
  ResetResponse,
24
25
  Resource as ResourceModel,
25
26
  ResourceType,
27
+ ResourceMode,
26
28
  HealthResponse,
27
29
  ExecuteFunctionRequest,
28
30
  ExecuteFunctionResponse,
@@ -35,6 +37,7 @@ logger = logging.getLogger(__name__)
35
37
  RESOURCE_TYPES = {
36
38
  ResourceType.db: SQLiteResource,
37
39
  ResourceType.cdp: BrowserResource,
40
+ ResourceType.api: APIResource,
38
41
  }
39
42
 
40
43
  ValidatorType = Callable[
@@ -100,6 +103,29 @@ class InstanceClient:
100
103
  self._resources_state[ResourceType.cdp.value][name], self.client
101
104
  )
102
105
 
106
+ def api(self, name: str, base_url: str) -> APIResource:
107
+ """
108
+ Returns an API resource for making HTTP requests.
109
+
110
+ Args:
111
+ name: The name of the API resource
112
+ base_url: The base URL for API requests
113
+
114
+ Returns:
115
+ An APIResource for making HTTP requests
116
+ """
117
+ # Create a minimal resource model for API
118
+ resource_model = ResourceModel(
119
+ name=name,
120
+ type=ResourceType.api,
121
+ mode=ResourceMode.rw,
122
+ )
123
+ return APIResource(
124
+ resource_model,
125
+ base_url=base_url,
126
+ client=self.client.httpx_client if self.client else None,
127
+ )
128
+
103
129
  def resources(self) -> List[Resource]:
104
130
  self._load_resources()
105
131
  return [
@@ -91,6 +91,7 @@ class ResourceMode(Enum):
91
91
  class ResourceType(Enum):
92
92
  db = "sqlite"
93
93
  cdp = "cdp"
94
+ api = "api"
94
95
 
95
96
 
96
97
  class TableSchema(BaseModel):
@@ -0,0 +1,200 @@
1
+ """API Resource for making HTTP requests to the app's API endpoint."""
2
+
3
+ from typing import Any, Dict, Optional, Union
4
+ import httpx
5
+
6
+ from .base import Resource
7
+ from ..instance.models import Resource as ResourceModel
8
+
9
+
10
+ class APIResponse:
11
+ """Simple wrapper around httpx.Response with a requests-like interface."""
12
+
13
+ def __init__(self, response: httpx.Response):
14
+ self._response = response
15
+ self.status_code: int = response.status_code
16
+ self.headers: httpx.Headers = response.headers
17
+ self.text: str = response.text
18
+ self.content: bytes = response.content
19
+ self.url: str = str(response.url)
20
+ self.ok: bool = response.is_success
21
+
22
+ def json(self) -> Any:
23
+ """Parse response body as JSON."""
24
+ return self._response.json()
25
+
26
+ def raise_for_status(self) -> "APIResponse":
27
+ """Raise an HTTPStatusError if the response has an error status code."""
28
+ self._response.raise_for_status()
29
+ return self
30
+
31
+ def __repr__(self) -> str:
32
+ return f"<APIResponse [{self.status_code}]>"
33
+
34
+
35
+ class APIResource(Resource):
36
+ """HTTP client for making requests to the app's API endpoint.
37
+
38
+ Provides a requests-like interface for interacting with the app's REST API.
39
+
40
+ Example:
41
+ api = env.api()
42
+ response = api.get("/users/1")
43
+ print(response.status_code) # 200
44
+ print(response.json()) # {"id": 1, "name": "John"}
45
+
46
+ # With headers/auth
47
+ response = api.post(
48
+ "/users",
49
+ json={"name": "Jane"},
50
+ headers={"Authorization": "Bearer xxx"}
51
+ )
52
+ """
53
+
54
+ def __init__(
55
+ self,
56
+ resource: ResourceModel,
57
+ base_url: str,
58
+ client: Optional[httpx.Client] = None,
59
+ ):
60
+ super().__init__(resource)
61
+ self.base_url = base_url.rstrip("/")
62
+ self._client = client or httpx.Client()
63
+
64
+ def _build_url(self, path: str) -> str:
65
+ """Build full URL from base_url and path."""
66
+ if path.startswith("/"):
67
+ return f"{self.base_url}{path}"
68
+ return f"{self.base_url}/{path}"
69
+
70
+ def request(
71
+ self,
72
+ method: str,
73
+ path: str,
74
+ *,
75
+ params: Optional[Dict[str, Any]] = None,
76
+ json: Optional[Any] = None,
77
+ data: Optional[Dict[str, Any]] = None,
78
+ headers: Optional[Dict[str, str]] = None,
79
+ cookies: Optional[Dict[str, str]] = None,
80
+ timeout: Optional[float] = None,
81
+ **kwargs: Any,
82
+ ) -> APIResponse:
83
+ """Make an HTTP request.
84
+
85
+ Args:
86
+ method: HTTP method (GET, POST, PUT, PATCH, DELETE, etc.)
87
+ path: URL path (relative to base_url)
88
+ params: Query parameters
89
+ json: JSON body (will be serialized)
90
+ data: Form data
91
+ headers: Request headers
92
+ cookies: Request cookies
93
+ timeout: Request timeout in seconds
94
+ **kwargs: Additional arguments passed to httpx
95
+
96
+ Returns:
97
+ APIResponse with status_code, headers, text, content, json() method
98
+ """
99
+ url = self._build_url(path)
100
+ response = self._client.request(
101
+ method,
102
+ url,
103
+ params=params,
104
+ json=json,
105
+ data=data,
106
+ headers=headers,
107
+ cookies=cookies,
108
+ timeout=timeout,
109
+ **kwargs,
110
+ )
111
+ return APIResponse(response)
112
+
113
+ def get(
114
+ self,
115
+ path: str,
116
+ *,
117
+ params: Optional[Dict[str, Any]] = None,
118
+ headers: Optional[Dict[str, str]] = None,
119
+ **kwargs: Any,
120
+ ) -> APIResponse:
121
+ """Make a GET request."""
122
+ return self.request("GET", path, params=params, headers=headers, **kwargs)
123
+
124
+ def post(
125
+ self,
126
+ path: str,
127
+ *,
128
+ json: Optional[Any] = None,
129
+ data: Optional[Dict[str, Any]] = None,
130
+ params: Optional[Dict[str, Any]] = None,
131
+ headers: Optional[Dict[str, str]] = None,
132
+ **kwargs: Any,
133
+ ) -> APIResponse:
134
+ """Make a POST request."""
135
+ return self.request(
136
+ "POST", path, json=json, data=data, params=params, headers=headers, **kwargs
137
+ )
138
+
139
+ def put(
140
+ self,
141
+ path: str,
142
+ *,
143
+ json: Optional[Any] = None,
144
+ data: Optional[Dict[str, Any]] = None,
145
+ params: Optional[Dict[str, Any]] = None,
146
+ headers: Optional[Dict[str, str]] = None,
147
+ **kwargs: Any,
148
+ ) -> APIResponse:
149
+ """Make a PUT request."""
150
+ return self.request(
151
+ "PUT", path, json=json, data=data, params=params, headers=headers, **kwargs
152
+ )
153
+
154
+ def patch(
155
+ self,
156
+ path: str,
157
+ *,
158
+ json: Optional[Any] = None,
159
+ data: Optional[Dict[str, Any]] = None,
160
+ params: Optional[Dict[str, Any]] = None,
161
+ headers: Optional[Dict[str, str]] = None,
162
+ **kwargs: Any,
163
+ ) -> APIResponse:
164
+ """Make a PATCH request."""
165
+ return self.request(
166
+ "PATCH", path, json=json, data=data, params=params, headers=headers, **kwargs
167
+ )
168
+
169
+ def delete(
170
+ self,
171
+ path: str,
172
+ *,
173
+ params: Optional[Dict[str, Any]] = None,
174
+ headers: Optional[Dict[str, str]] = None,
175
+ **kwargs: Any,
176
+ ) -> APIResponse:
177
+ """Make a DELETE request."""
178
+ return self.request("DELETE", path, params=params, headers=headers, **kwargs)
179
+
180
+ def head(
181
+ self,
182
+ path: str,
183
+ *,
184
+ params: Optional[Dict[str, Any]] = None,
185
+ headers: Optional[Dict[str, str]] = None,
186
+ **kwargs: Any,
187
+ ) -> APIResponse:
188
+ """Make a HEAD request."""
189
+ return self.request("HEAD", path, params=params, headers=headers, **kwargs)
190
+
191
+ def options(
192
+ self,
193
+ path: str,
194
+ *,
195
+ params: Optional[Dict[str, Any]] = None,
196
+ headers: Optional[Dict[str, str]] = None,
197
+ **kwargs: Any,
198
+ ) -> APIResponse:
199
+ """Make an OPTIONS request."""
200
+ return self.request("OPTIONS", path, params=params, headers=headers, **kwargs)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: fleet-python
3
- Version: 0.2.95
3
+ Version: 0.2.96
4
4
  Summary: Python SDK for Fleet environments
5
5
  Author-email: Fleet AI <nic@fleet.so>
6
6
  License: Apache-2.0
@@ -49,6 +49,7 @@ fleet/_async/instance/__init__.py
49
49
  fleet/_async/instance/base.py
50
50
  fleet/_async/instance/client.py
51
51
  fleet/_async/resources/__init__.py
52
+ fleet/_async/resources/api.py
52
53
  fleet/_async/resources/base.py
53
54
  fleet/_async/resources/browser.py
54
55
  fleet/_async/resources/mcp.py
@@ -81,6 +82,7 @@ fleet/proxy/__init__.py
81
82
  fleet/proxy/proxy.py
82
83
  fleet/proxy/whitelist.py
83
84
  fleet/resources/__init__.py
85
+ fleet/resources/api.py
84
86
  fleet/resources/base.py
85
87
  fleet/resources/browser.py
86
88
  fleet/resources/mcp.py
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
  [project]
6
6
  name = "fleet-python"
7
7
 
8
- version = "0.2.95"
8
+ version = "0.2.96"
9
9
  description = "Python SDK for Fleet environments"
10
10
  authors = [
11
11
  {name = "Fleet AI", email = "nic@fleet.so"},
File without changes
File without changes
File without changes