futurehouse-client 0.3.20.dev411__tar.gz → 0.4.1__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 (34) hide show
  1. {futurehouse_client-0.3.20.dev411/futurehouse_client.egg-info → futurehouse_client-0.4.1}/PKG-INFO +1 -6
  2. {futurehouse_client-0.3.20.dev411 → futurehouse_client-0.4.1}/futurehouse_client/clients/rest_client.py +33 -41
  3. {futurehouse_client-0.3.20.dev411 → futurehouse_client-0.4.1}/futurehouse_client/models/app.py +4 -7
  4. futurehouse_client-0.4.1/futurehouse_client/utils/general.py +29 -0
  5. {futurehouse_client-0.3.20.dev411 → futurehouse_client-0.4.1}/futurehouse_client/utils/world_model_tools.py +3 -2
  6. futurehouse_client-0.4.1/futurehouse_client/version.py +21 -0
  7. {futurehouse_client-0.3.20.dev411 → futurehouse_client-0.4.1/futurehouse_client.egg-info}/PKG-INFO +1 -6
  8. {futurehouse_client-0.3.20.dev411 → futurehouse_client-0.4.1}/futurehouse_client.egg-info/SOURCES.txt +0 -2
  9. {futurehouse_client-0.3.20.dev411 → futurehouse_client-0.4.1}/futurehouse_client.egg-info/requires.txt +0 -5
  10. {futurehouse_client-0.3.20.dev411 → futurehouse_client-0.4.1}/pyproject.toml +0 -5
  11. {futurehouse_client-0.3.20.dev411 → futurehouse_client-0.4.1}/tests/test_rest.py +3 -0
  12. futurehouse_client-0.3.20.dev411/futurehouse_client/clients/data_storage_methods.py +0 -1867
  13. futurehouse_client-0.3.20.dev411/futurehouse_client/models/data_storage_methods.py +0 -333
  14. futurehouse_client-0.3.20.dev411/futurehouse_client/utils/general.py +0 -63
  15. futurehouse_client-0.3.20.dev411/futurehouse_client/version.py +0 -34
  16. {futurehouse_client-0.3.20.dev411 → futurehouse_client-0.4.1}/LICENSE +0 -0
  17. {futurehouse_client-0.3.20.dev411 → futurehouse_client-0.4.1}/README.md +0 -0
  18. {futurehouse_client-0.3.20.dev411 → futurehouse_client-0.4.1}/docs/__init__.py +0 -0
  19. {futurehouse_client-0.3.20.dev411 → futurehouse_client-0.4.1}/docs/client_notebook.ipynb +0 -0
  20. {futurehouse_client-0.3.20.dev411 → futurehouse_client-0.4.1}/futurehouse_client/__init__.py +0 -0
  21. {futurehouse_client-0.3.20.dev411 → futurehouse_client-0.4.1}/futurehouse_client/clients/__init__.py +0 -0
  22. {futurehouse_client-0.3.20.dev411 → futurehouse_client-0.4.1}/futurehouse_client/clients/job_client.py +0 -0
  23. {futurehouse_client-0.3.20.dev411 → futurehouse_client-0.4.1}/futurehouse_client/models/__init__.py +0 -0
  24. {futurehouse_client-0.3.20.dev411 → futurehouse_client-0.4.1}/futurehouse_client/models/client.py +0 -0
  25. {futurehouse_client-0.3.20.dev411 → futurehouse_client-0.4.1}/futurehouse_client/models/rest.py +0 -0
  26. {futurehouse_client-0.3.20.dev411 → futurehouse_client-0.4.1}/futurehouse_client/py.typed +0 -0
  27. {futurehouse_client-0.3.20.dev411 → futurehouse_client-0.4.1}/futurehouse_client/utils/__init__.py +0 -0
  28. {futurehouse_client-0.3.20.dev411 → futurehouse_client-0.4.1}/futurehouse_client/utils/auth.py +0 -0
  29. {futurehouse_client-0.3.20.dev411 → futurehouse_client-0.4.1}/futurehouse_client/utils/module_utils.py +0 -0
  30. {futurehouse_client-0.3.20.dev411 → futurehouse_client-0.4.1}/futurehouse_client/utils/monitoring.py +0 -0
  31. {futurehouse_client-0.3.20.dev411 → futurehouse_client-0.4.1}/futurehouse_client.egg-info/dependency_links.txt +0 -0
  32. {futurehouse_client-0.3.20.dev411 → futurehouse_client-0.4.1}/futurehouse_client.egg-info/top_level.txt +0 -0
  33. {futurehouse_client-0.3.20.dev411 → futurehouse_client-0.4.1}/setup.cfg +0 -0
  34. {futurehouse_client-0.3.20.dev411 → futurehouse_client-0.4.1}/tests/test_client.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: futurehouse-client
3
- Version: 0.3.20.dev411
3
+ Version: 0.4.1
4
4
  Summary: A client for interacting with endpoints of the FutureHouse service.
5
5
  Author-email: FutureHouse technical staff <hello@futurehouse.org>
6
6
  License: Apache License
@@ -213,22 +213,18 @@ Classifier: Programming Language :: Python
213
213
  Requires-Python: <3.14,>=3.11
214
214
  Description-Content-Type: text/markdown
215
215
  License-File: LICENSE
216
- Requires-Dist: aiofiles
217
216
  Requires-Dist: cloudpickle
218
217
  Requires-Dist: fhaviary
219
- Requires-Dist: google-resumable-media[aiohttp]
220
218
  Requires-Dist: httpx
221
219
  Requires-Dist: ldp>=0.22.0
222
220
  Requires-Dist: litellm
223
221
  Requires-Dist: pydantic
224
222
  Requires-Dist: python-dotenv
225
- Requires-Dist: requests
226
223
  Requires-Dist: tenacity
227
224
  Requires-Dist: tqdm>=4.62
228
225
  Provides-Extra: dev
229
226
  Requires-Dist: black; extra == "dev"
230
227
  Requires-Dist: futurehouse-client[monitoring,typing]; extra == "dev"
231
- Requires-Dist: ipykernel; extra == "dev"
232
228
  Requires-Dist: jupyter; extra == "dev"
233
229
  Requires-Dist: jupyterlab; extra == "dev"
234
230
  Requires-Dist: mypy; extra == "dev"
@@ -248,7 +244,6 @@ Requires-Dist: setuptools_scm; extra == "dev"
248
244
  Provides-Extra: monitoring
249
245
  Requires-Dist: newrelic>=8.8.0; extra == "monitoring"
250
246
  Provides-Extra: typing
251
- Requires-Dist: types-PyYAML; extra == "typing"
252
247
  Requires-Dist: types-requests; extra == "typing"
253
248
  Requires-Dist: types-tqdm; extra == "typing"
254
249
  Dynamic: license-file
@@ -26,14 +26,21 @@ from httpx import (
26
26
  AsyncClient,
27
27
  Client,
28
28
  CloseError,
29
+ ConnectError,
30
+ ConnectTimeout,
29
31
  HTTPStatusError,
32
+ NetworkError,
33
+ ReadError,
34
+ ReadTimeout,
30
35
  RemoteProtocolError,
31
36
  codes,
32
37
  )
33
38
  from ldp.agent import AgentConfig
39
+ from requests.exceptions import RequestException, Timeout
34
40
  from tenacity import (
35
41
  before_sleep_log,
36
42
  retry,
43
+ retry_if_exception_type,
37
44
  stop_after_attempt,
38
45
  wait_exponential,
39
46
  )
@@ -41,7 +48,6 @@ from tqdm import tqdm as sync_tqdm
41
48
  from tqdm.asyncio import tqdm
42
49
 
43
50
  from futurehouse_client.clients import JobNames
44
- from futurehouse_client.clients.data_storage_methods import DataStorageMethods
45
51
  from futurehouse_client.models.app import (
46
52
  AuthType,
47
53
  JobDeploymentConfig,
@@ -62,10 +68,7 @@ from futurehouse_client.models.rest import (
62
68
  WorldModelResponse,
63
69
  )
64
70
  from futurehouse_client.utils.auth import RefreshingJWT
65
- from futurehouse_client.utils.general import (
66
- create_retry_if_connection_error,
67
- gather_with_concurrency,
68
- )
71
+ from futurehouse_client.utils.general import gather_with_concurrency
69
72
  from futurehouse_client.utils.module_utils import (
70
73
  OrganizationSelector,
71
74
  fetch_environment_function_docstring,
@@ -157,15 +160,28 @@ class FileUploadError(RestClientError):
157
160
  """Raised when there's an error uploading a file."""
158
161
 
159
162
 
160
- retry_if_connection_error = create_retry_if_connection_error(FileUploadError)
163
+ retry_if_connection_error = retry_if_exception_type((
164
+ # From requests
165
+ Timeout,
166
+ ConnectionError,
167
+ RequestException,
168
+ # From httpx
169
+ ConnectError,
170
+ ConnectTimeout,
171
+ ReadTimeout,
172
+ ReadError,
173
+ NetworkError,
174
+ RemoteProtocolError,
175
+ CloseError,
176
+ FileUploadError,
177
+ ))
161
178
 
162
179
  DEFAULT_AGENT_TIMEOUT: int = 2400 # seconds
163
180
 
164
181
 
165
182
  # pylint: disable=too-many-public-methods
166
- class RestClient(DataStorageMethods):
167
- REQUEST_TIMEOUT: ClassVar[float] = 30.0 # sec - for general API calls
168
- FILE_UPLOAD_TIMEOUT: ClassVar[float] = 600.0 # 10 minutes - for file uploads
183
+ class RestClient:
184
+ REQUEST_TIMEOUT: ClassVar[float] = 30.0 # sec
169
185
  MAX_RETRY_ATTEMPTS: ClassVar[int] = 3
170
186
  RETRY_MULTIPLIER: ClassVar[int] = 1
171
187
  MAX_RETRY_WAIT: ClassVar[int] = 10
@@ -223,35 +239,11 @@ class RestClient(DataStorageMethods):
223
239
  """Authenticated HTTP client for multipart uploads."""
224
240
  return cast(Client, self.get_client(None, authenticated=True))
225
241
 
226
- @property
227
- def file_upload_client(self) -> Client:
228
- """Authenticated HTTP client with extended timeout for file uploads."""
229
- return cast(
230
- Client,
231
- self.get_client(
232
- "application/json", authenticated=True, timeout=self.FILE_UPLOAD_TIMEOUT
233
- ),
234
- )
235
-
236
- @property
237
- def async_file_upload_client(self) -> AsyncClient:
238
- """Authenticated async HTTP client with extended timeout for file uploads."""
239
- return cast(
240
- AsyncClient,
241
- self.get_client(
242
- "application/json",
243
- authenticated=True,
244
- async_client=True,
245
- timeout=self.FILE_UPLOAD_TIMEOUT,
246
- ),
247
- )
248
-
249
242
  def get_client(
250
243
  self,
251
244
  content_type: str | None = "application/json",
252
245
  authenticated: bool = True,
253
246
  async_client: bool = False,
254
- timeout: float | None = None,
255
247
  ) -> Client | AsyncClient:
256
248
  """Return a cached HTTP client or create one if needed.
257
249
 
@@ -259,13 +251,12 @@ class RestClient(DataStorageMethods):
259
251
  content_type: The desired content type header. Use None for multipart uploads.
260
252
  authenticated: Whether the client should include authentication.
261
253
  async_client: Whether to use an async client.
262
- timeout: Custom timeout in seconds. Uses REQUEST_TIMEOUT if not provided.
263
254
 
264
255
  Returns:
265
256
  An HTTP client configured with the appropriate headers.
266
257
  """
267
- client_timeout = timeout or self.REQUEST_TIMEOUT
268
- key = f"{content_type or 'multipart'}_{authenticated}_{async_client}_{client_timeout}"
258
+ # Create a composite key based on content type and auth flag
259
+ key = f"{content_type or 'multipart'}_{authenticated}_{async_client}"
269
260
 
270
261
  if key not in self._clients:
271
262
  headers = copy.deepcopy(self.headers)
@@ -291,14 +282,14 @@ class RestClient(DataStorageMethods):
291
282
  AsyncClient(
292
283
  base_url=self.base_url,
293
284
  headers=headers,
294
- timeout=client_timeout,
285
+ timeout=self.REQUEST_TIMEOUT,
295
286
  auth=auth,
296
287
  )
297
288
  if async_client
298
289
  else Client(
299
290
  base_url=self.base_url,
300
291
  headers=headers,
301
- timeout=client_timeout,
292
+ timeout=self.REQUEST_TIMEOUT,
302
293
  auth=auth,
303
294
  )
304
295
  )
@@ -1630,16 +1621,17 @@ class RestClient(DataStorageMethods):
1630
1621
  A list of world model names.
1631
1622
  """
1632
1623
  try:
1624
+ # Use the consolidated endpoint with search parameters
1633
1625
  response = self.client.get(
1634
- "/v0.1/world-models/search/",
1626
+ "/v0.1/world-models",
1635
1627
  params={
1636
- "query": query,
1628
+ "q": query,
1637
1629
  "size": size,
1638
- "total_search_size": total_search_size,
1639
1630
  "search_all_versions": search_all_versions,
1640
1631
  },
1641
1632
  )
1642
1633
  response.raise_for_status()
1634
+ # The new endpoint returns a list of models directly
1643
1635
  return response.json()
1644
1636
  except HTTPStatusError as e:
1645
1637
  raise WorldModelFetchError(
@@ -27,10 +27,7 @@ if TYPE_CHECKING:
27
27
  MAX_CROW_JOB_RUN_TIMEOUT = 60 * 60 * 24 # 24 hours in sec
28
28
  MIN_CROW_JOB_RUN_TIMEOUT = 0 # sec
29
29
 
30
-
31
- class PythonVersion(StrEnum):
32
- V3_11 = "3.11"
33
- V3_12 = "3.12"
30
+ DEFAULT_PYTHON_VERSION_USED_FOR_JOB_BUILDS = "3.13"
34
31
 
35
32
 
36
33
  class AuthType(StrEnum):
@@ -420,9 +417,9 @@ class JobDeploymentConfig(BaseModel):
420
417
  description="The configuration for the cloud run container.",
421
418
  )
422
419
 
423
- python_version: PythonVersion = Field(
424
- default=PythonVersion.V3_12,
425
- description="The python version your docker image should build with.",
420
+ python_version: str = Field(
421
+ default=DEFAULT_PYTHON_VERSION_USED_FOR_JOB_BUILDS,
422
+ description="The python version your docker image should build with (e.g., '3.11', '3.12', '3.13').",
426
423
  )
427
424
 
428
425
  agent: Agent | AgentConfig | str = Field(
@@ -0,0 +1,29 @@
1
+ import asyncio
2
+ from collections.abc import Awaitable, Iterable
3
+ from typing import TypeVar
4
+
5
+ from tqdm.asyncio import tqdm
6
+
7
+ T = TypeVar("T")
8
+
9
+
10
+ async def gather_with_concurrency(
11
+ n: int | asyncio.Semaphore, coros: Iterable[Awaitable[T]], progress: bool = False
12
+ ) -> list[T]:
13
+ """
14
+ Run asyncio.gather with a concurrency limit.
15
+
16
+ SEE: https://stackoverflow.com/a/61478547/2392535
17
+ """
18
+ semaphore = asyncio.Semaphore(n) if isinstance(n, int) else n
19
+
20
+ async def sem_coro(coro: Awaitable[T]) -> T:
21
+ async with semaphore:
22
+ return await coro
23
+
24
+ if progress:
25
+ return await tqdm.gather(
26
+ *(sem_coro(c) for c in coros), desc="Gathering", ncols=0
27
+ )
28
+
29
+ return await asyncio.gather(*(sem_coro(c) for c in coros))
@@ -46,16 +46,17 @@ class WorldModelTools:
46
46
  return WorldModelTools._get_client().create_world_model(world_model)
47
47
 
48
48
  @staticmethod
49
- def search_world_models(query: str) -> list[str]:
49
+ def search_world_models(query: str, size: int = 10) -> list[str]:
50
50
  """Search for world models using a text query.
51
51
 
52
52
  Args:
53
53
  query: The search query string to match against world model content.
54
+ size: The number of results to return (default: 10).
54
55
 
55
56
  Returns:
56
57
  list[str]: A list of world model IDs that match the search query.
57
58
  """
58
- return WorldModelTools._get_client().search_world_models(query, size=1)
59
+ return WorldModelTools._get_client().search_world_models(query, size=size)
59
60
 
60
61
 
61
62
  create_world_model_tool = Tool.from_function(WorldModelTools.create_world_model)
@@ -0,0 +1,21 @@
1
+ # file generated by setuptools-scm
2
+ # don't change, don't track in version control
3
+
4
+ __all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
5
+
6
+ TYPE_CHECKING = False
7
+ if TYPE_CHECKING:
8
+ from typing import Tuple
9
+ from typing import Union
10
+
11
+ VERSION_TUPLE = Tuple[Union[int, str], ...]
12
+ else:
13
+ VERSION_TUPLE = object
14
+
15
+ version: str
16
+ __version__: str
17
+ __version_tuple__: VERSION_TUPLE
18
+ version_tuple: VERSION_TUPLE
19
+
20
+ __version__ = version = '0.4.1'
21
+ __version_tuple__ = version_tuple = (0, 4, 1)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: futurehouse-client
3
- Version: 0.3.20.dev411
3
+ Version: 0.4.1
4
4
  Summary: A client for interacting with endpoints of the FutureHouse service.
5
5
  Author-email: FutureHouse technical staff <hello@futurehouse.org>
6
6
  License: Apache License
@@ -213,22 +213,18 @@ Classifier: Programming Language :: Python
213
213
  Requires-Python: <3.14,>=3.11
214
214
  Description-Content-Type: text/markdown
215
215
  License-File: LICENSE
216
- Requires-Dist: aiofiles
217
216
  Requires-Dist: cloudpickle
218
217
  Requires-Dist: fhaviary
219
- Requires-Dist: google-resumable-media[aiohttp]
220
218
  Requires-Dist: httpx
221
219
  Requires-Dist: ldp>=0.22.0
222
220
  Requires-Dist: litellm
223
221
  Requires-Dist: pydantic
224
222
  Requires-Dist: python-dotenv
225
- Requires-Dist: requests
226
223
  Requires-Dist: tenacity
227
224
  Requires-Dist: tqdm>=4.62
228
225
  Provides-Extra: dev
229
226
  Requires-Dist: black; extra == "dev"
230
227
  Requires-Dist: futurehouse-client[monitoring,typing]; extra == "dev"
231
- Requires-Dist: ipykernel; extra == "dev"
232
228
  Requires-Dist: jupyter; extra == "dev"
233
229
  Requires-Dist: jupyterlab; extra == "dev"
234
230
  Requires-Dist: mypy; extra == "dev"
@@ -248,7 +244,6 @@ Requires-Dist: setuptools_scm; extra == "dev"
248
244
  Provides-Extra: monitoring
249
245
  Requires-Dist: newrelic>=8.8.0; extra == "monitoring"
250
246
  Provides-Extra: typing
251
- Requires-Dist: types-PyYAML; extra == "typing"
252
247
  Requires-Dist: types-requests; extra == "typing"
253
248
  Requires-Dist: types-tqdm; extra == "typing"
254
249
  Dynamic: license-file
@@ -12,13 +12,11 @@ futurehouse_client.egg-info/dependency_links.txt
12
12
  futurehouse_client.egg-info/requires.txt
13
13
  futurehouse_client.egg-info/top_level.txt
14
14
  futurehouse_client/clients/__init__.py
15
- futurehouse_client/clients/data_storage_methods.py
16
15
  futurehouse_client/clients/job_client.py
17
16
  futurehouse_client/clients/rest_client.py
18
17
  futurehouse_client/models/__init__.py
19
18
  futurehouse_client/models/app.py
20
19
  futurehouse_client/models/client.py
21
- futurehouse_client/models/data_storage_methods.py
22
20
  futurehouse_client/models/rest.py
23
21
  futurehouse_client/utils/__init__.py
24
22
  futurehouse_client/utils/auth.py
@@ -1,20 +1,16 @@
1
- aiofiles
2
1
  cloudpickle
3
2
  fhaviary
4
- google-resumable-media[aiohttp]
5
3
  httpx
6
4
  ldp>=0.22.0
7
5
  litellm
8
6
  pydantic
9
7
  python-dotenv
10
- requests
11
8
  tenacity
12
9
  tqdm>=4.62
13
10
 
14
11
  [dev]
15
12
  black
16
13
  futurehouse-client[monitoring,typing]
17
- ipykernel
18
14
  jupyter
19
15
  jupyterlab
20
16
  mypy
@@ -36,6 +32,5 @@ setuptools_scm
36
32
  newrelic>=8.8.0
37
33
 
38
34
  [typing]
39
- types-PyYAML
40
35
  types-requests
41
36
  types-tqdm
@@ -17,16 +17,13 @@ classifiers = [
17
17
  "Programming Language :: Python",
18
18
  ]
19
19
  dependencies = [
20
- "aiofiles",
21
20
  "cloudpickle",
22
21
  "fhaviary",
23
- "google-resumable-media[aiohttp]",
24
22
  "httpx",
25
23
  "ldp>=0.22.0",
26
24
  "litellm",
27
25
  "pydantic",
28
26
  "python-dotenv",
29
- "requests",
30
27
  "tenacity",
31
28
  "tqdm>=4.62", # For tqdm.asyncio stdlib consistency
32
29
  ]
@@ -41,7 +38,6 @@ requires-python = ">=3.11,<3.14"
41
38
  dev = [
42
39
  "black",
43
40
  "futurehouse-client[monitoring,typing]",
44
- "ipykernel",
45
41
  "jupyter",
46
42
  "jupyterlab",
47
43
  "mypy",
@@ -63,7 +59,6 @@ monitoring = [
63
59
  "newrelic>=8.8.0",
64
60
  ]
65
61
  typing = [
66
- "types-PyYAML",
67
62
  "types-requests",
68
63
  "types-tqdm",
69
64
  ]
@@ -978,6 +978,7 @@ class TestUserAgentRequestOperations:
978
978
  payload = UserAgentRequestPostPayload(
979
979
  trajectory_id=running_trajectory_id,
980
980
  request={"question": "Do you approve?"},
981
+ notify_user={"email": False, "sms": False}, # avoid sending notifications
981
982
  )
982
983
  request_id = admin_client.create_user_agent_request(payload)
983
984
  assert isinstance(request_id, UUID)
@@ -1045,6 +1046,7 @@ class TestAsyncUserAgentRequestOperations:
1045
1046
  query="Why would I follow up on this query?",
1046
1047
  ).model_dump(mode="json"),
1047
1048
  expires_in_seconds=10,
1049
+ notify_user={"email": False, "sms": False}, # avoid sending notifications
1048
1050
  )
1049
1051
 
1050
1052
  request_id = await admin_client.acreate_user_agent_request(payload)
@@ -1097,6 +1099,7 @@ class TestAsyncUserAgentRequestOperations:
1097
1099
  name=JobNames.from_string("dummy"),
1098
1100
  query="Why would I follow up on this query?",
1099
1101
  ).model_dump(mode="json"),
1102
+ notify_user={"email": False, "sms": False}, # avoid sending notifications
1100
1103
  )
1101
1104
 
1102
1105
  request_id = await admin_client.acreate_user_agent_request(payload)