futurehouse-client 0.3.18.dev185__py3-none-any.whl → 0.3.18.dev195__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.
@@ -15,7 +15,7 @@ import uuid
15
15
  from collections.abc import Collection
16
16
  from pathlib import Path
17
17
  from types import ModuleType
18
- from typing import Any, ClassVar, assert_never, cast
18
+ from typing import Any, ClassVar, cast
19
19
  from uuid import UUID
20
20
 
21
21
  import cloudpickle
@@ -45,7 +45,6 @@ from tqdm.asyncio import tqdm
45
45
 
46
46
  from futurehouse_client.clients import JobNames
47
47
  from futurehouse_client.models.app import (
48
- APIKeyPayload,
49
48
  AuthType,
50
49
  JobDeploymentConfig,
51
50
  PQATaskResponse,
@@ -55,11 +54,7 @@ from futurehouse_client.models.app import (
55
54
  TaskResponseVerbose,
56
55
  )
57
56
  from futurehouse_client.models.rest import ExecutionStatus
58
- from futurehouse_client.utils.auth import (
59
- AUTH_ERRORS_TO_RETRY_ON,
60
- AuthError,
61
- refresh_token_on_auth_error,
62
- )
57
+ from futurehouse_client.utils.auth import RefreshingJWT
63
58
  from futurehouse_client.utils.general import gather_with_concurrency
64
59
  from futurehouse_client.utils.module_utils import (
65
60
  OrganizationSelector,
@@ -128,8 +123,6 @@ retry_if_connection_error = retry_if_exception_type((
128
123
  FileUploadError,
129
124
  ))
130
125
 
131
- # 5 minute default for JWTs
132
- JWT_TOKEN_CACHE_EXPIRY: int = 300 # seconds
133
126
  DEFAULT_AGENT_TIMEOUT: int = 2400 # seconds
134
127
 
135
128
 
@@ -163,69 +156,85 @@ class RestClient:
163
156
  self.api_key = api_key
164
157
  self._clients: dict[str, Client | AsyncClient] = {}
165
158
  self.headers = headers or {}
166
- self.auth_jwt = self._run_auth(jwt=jwt)
159
+ self.jwt = jwt
167
160
  self.organizations: list[str] = self._filter_orgs(organization)
168
161
 
169
162
  @property
170
163
  def client(self) -> Client:
171
- """Lazily initialized and cached HTTP client with authentication."""
172
- return cast(Client, self.get_client("application/json", with_auth=True))
164
+ """Authenticated HTTP client for regular API calls."""
165
+ return cast(Client, self.get_client("application/json", authenticated=True))
173
166
 
174
167
  @property
175
168
  def async_client(self) -> AsyncClient:
176
- """Lazily initialized and cached HTTP client with authentication."""
169
+ """Authenticated async HTTP client for regular API calls."""
177
170
  return cast(
178
171
  AsyncClient,
179
- self.get_client("application/json", with_auth=True, with_async=True),
172
+ self.get_client("application/json", authenticated=True, async_client=True),
180
173
  )
181
174
 
182
175
  @property
183
- def auth_client(self) -> Client:
184
- """Lazily initialized and cached HTTP client without authentication."""
185
- return cast(Client, self.get_client("application/json", with_auth=False))
176
+ def unauthenticated_client(self) -> Client:
177
+ """Unauthenticated HTTP client for auth operations to avoid recursion."""
178
+ return cast(Client, self.get_client("application/json", authenticated=False))
186
179
 
187
180
  @property
188
181
  def multipart_client(self) -> Client:
189
- """Lazily initialized and cached HTTP client for multipart uploads."""
190
- return cast(Client, self.get_client(None, with_auth=True))
182
+ """Authenticated HTTP client for multipart uploads."""
183
+ return cast(Client, self.get_client(None, authenticated=True))
191
184
 
192
185
  def get_client(
193
186
  self,
194
187
  content_type: str | None = "application/json",
195
- with_auth: bool = True,
196
- with_async: bool = False,
188
+ authenticated: bool = True,
189
+ async_client: bool = False,
197
190
  ) -> Client | AsyncClient:
198
191
  """Return a cached HTTP client or create one if needed.
199
192
 
200
193
  Args:
201
194
  content_type: The desired content type header. Use None for multipart uploads.
202
- with_auth: Whether the client should include an Authorization header.
203
- with_async: Whether to use an async client.
195
+ authenticated: Whether the client should include authentication.
196
+ async_client: Whether to use an async client.
204
197
 
205
198
  Returns:
206
199
  An HTTP client configured with the appropriate headers.
207
200
  """
208
- # Create a composite key based on content type and auth flag.
209
- key = f"{content_type or 'multipart'}_{with_auth}_{with_async}"
201
+ # Create a composite key based on content type and auth flag
202
+ key = f"{content_type or 'multipart'}_{authenticated}_{async_client}"
203
+
210
204
  if key not in self._clients:
211
205
  headers = copy.deepcopy(self.headers)
212
- if with_auth:
213
- headers["Authorization"] = f"Bearer {self.auth_jwt}"
206
+ auth = None
207
+
208
+ if authenticated:
209
+ auth = RefreshingJWT(
210
+ # authenticated=False will always return a synchronous client
211
+ auth_client=cast(
212
+ Client, self.get_client("application/json", authenticated=False)
213
+ ),
214
+ auth_type=self.auth_type,
215
+ api_key=self.api_key,
216
+ jwt=self.jwt,
217
+ )
218
+
214
219
  if content_type:
215
220
  headers["Content-Type"] = content_type
221
+
216
222
  self._clients[key] = (
217
223
  AsyncClient(
218
224
  base_url=self.base_url,
219
225
  headers=headers,
220
226
  timeout=self.REQUEST_TIMEOUT,
227
+ auth=auth,
221
228
  )
222
- if with_async
229
+ if async_client
223
230
  else Client(
224
231
  base_url=self.base_url,
225
232
  headers=headers,
226
233
  timeout=self.REQUEST_TIMEOUT,
234
+ auth=auth,
227
235
  )
228
236
  )
237
+
229
238
  return self._clients[key]
230
239
 
231
240
  def close(self):
@@ -255,32 +264,6 @@ class RestClient:
255
264
  raise ValueError(f"Organization '{organization}' not found.")
256
265
  return filtered_orgs
257
266
 
258
- def _run_auth(self, jwt: str | None = None) -> str:
259
- auth_payload: APIKeyPayload | None
260
- if self.auth_type == AuthType.API_KEY:
261
- auth_payload = APIKeyPayload(api_key=self.api_key)
262
- elif self.auth_type == AuthType.JWT:
263
- auth_payload = None
264
- else:
265
- assert_never(self.auth_type)
266
- try:
267
- # Use the unauthenticated client for login
268
- if auth_payload:
269
- response = self.auth_client.post(
270
- "/auth/login", json=auth_payload.model_dump()
271
- )
272
- response.raise_for_status()
273
- token_data = response.json()
274
- elif jwt:
275
- token_data = {"access_token": jwt, "expires_in": JWT_TOKEN_CACHE_EXPIRY}
276
- else:
277
- raise ValueError("JWT token required for JWT authentication.")
278
-
279
- return token_data["access_token"]
280
- except Exception as e:
281
- raise RestClientError(f"Error authenticating: {e!s}") from e
282
-
283
- @refresh_token_on_auth_error()
284
267
  def _check_job(self, name: str, organization: str) -> dict[str, Any]:
285
268
  try:
286
269
  response = self.client.get(
@@ -288,19 +271,9 @@ class RestClient:
288
271
  )
289
272
  response.raise_for_status()
290
273
  return response.json()
291
- except HTTPStatusError as e:
292
- if e.response.status_code in AUTH_ERRORS_TO_RETRY_ON:
293
- raise AuthError(
294
- e.response.status_code,
295
- f"Authentication failed: {e}",
296
- request=e.request,
297
- response=e.response,
298
- ) from e
299
- raise
300
274
  except Exception as e:
301
275
  raise JobFetchError(f"Error checking job: {e!s}") from e
302
276
 
303
- @refresh_token_on_auth_error()
304
277
  def _fetch_my_orgs(self) -> list[str]:
305
278
  response = self.client.get(f"/v0.1/organizations?filter={True}")
306
279
  response.raise_for_status()
@@ -358,7 +331,6 @@ class RestClient:
358
331
  if not files:
359
332
  raise TaskFetchError(f"No files found in {path}")
360
333
 
361
- @refresh_token_on_auth_error()
362
334
  @retry(
363
335
  stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
364
336
  wait=wait_exponential(multiplier=RETRY_MULTIPLIER, max=MAX_RETRY_WAIT),
@@ -385,6 +357,7 @@ class RestClient:
385
357
  ),
386
358
  self.client.stream("GET", url, params={"history": history}) as response,
387
359
  ):
360
+ response.raise_for_status()
388
361
  json_data = "".join(response.iter_text(chunk_size=1024))
389
362
  data = json.loads(json_data)
390
363
  if "id" not in data:
@@ -399,19 +372,9 @@ class RestClient:
399
372
  ):
400
373
  return PQATaskResponse(**data)
401
374
  return TaskResponse(**data)
402
- except HTTPStatusError as e:
403
- if e.response.status_code in AUTH_ERRORS_TO_RETRY_ON:
404
- raise AuthError(
405
- e.response.status_code,
406
- f"Authentication failed: {e}",
407
- request=e.request,
408
- response=e.response,
409
- ) from e
410
- raise
411
375
  except Exception as e:
412
376
  raise TaskFetchError(f"Error getting task: {e!s}") from e
413
377
 
414
- @refresh_token_on_auth_error()
415
378
  @retry(
416
379
  stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
417
380
  wait=wait_exponential(multiplier=RETRY_MULTIPLIER, max=MAX_RETRY_WAIT),
@@ -455,19 +418,9 @@ class RestClient:
455
418
  ):
456
419
  return PQATaskResponse(**data)
457
420
  return TaskResponse(**data)
458
- except HTTPStatusError as e:
459
- if e.response.status_code in AUTH_ERRORS_TO_RETRY_ON:
460
- raise AuthError(
461
- e.response.status_code,
462
- f"Authentication failed: {e}",
463
- request=e.request,
464
- response=e.response,
465
- ) from e
466
- raise
467
421
  except Exception as e:
468
422
  raise TaskFetchError(f"Error getting task: {e!s}") from e
469
423
 
470
- @refresh_token_on_auth_error()
471
424
  @retry(
472
425
  stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
473
426
  wait=wait_exponential(multiplier=RETRY_MULTIPLIER, max=MAX_RETRY_WAIT),
@@ -491,20 +444,10 @@ class RestClient:
491
444
  response.raise_for_status()
492
445
  trajectory_id = response.json()["trajectory_id"]
493
446
  self.trajectory_id = trajectory_id
494
- except HTTPStatusError as e:
495
- if e.response.status_code in AUTH_ERRORS_TO_RETRY_ON:
496
- raise AuthError(
497
- e.response.status_code,
498
- f"Authentication failed: {e}",
499
- request=e.request,
500
- response=e.response,
501
- ) from e
502
- raise
503
447
  except Exception as e:
504
448
  raise TaskFetchError(f"Error creating task: {e!s}") from e
505
449
  return trajectory_id
506
450
 
507
- @refresh_token_on_auth_error()
508
451
  @retry(
509
452
  stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
510
453
  wait=wait_exponential(multiplier=RETRY_MULTIPLIER, max=MAX_RETRY_WAIT),
@@ -528,15 +471,6 @@ class RestClient:
528
471
  response.raise_for_status()
529
472
  trajectory_id = response.json()["trajectory_id"]
530
473
  self.trajectory_id = trajectory_id
531
- except HTTPStatusError as e:
532
- if e.response.status_code in AUTH_ERRORS_TO_RETRY_ON:
533
- raise AuthError(
534
- e.response.status_code,
535
- f"Authentication failed: {e}",
536
- request=e.request,
537
- response=e.response,
538
- ) from e
539
- raise
540
474
  except Exception as e:
541
475
  raise TaskFetchError(f"Error creating task: {e!s}") from e
542
476
  return trajectory_id
@@ -682,7 +616,6 @@ class RestClient:
682
616
  for task_id in trajectory_ids
683
617
  ]
684
618
 
685
- @refresh_token_on_auth_error()
686
619
  @retry(
687
620
  stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
688
621
  wait=wait_exponential(multiplier=RETRY_MULTIPLIER, max=MAX_RETRY_WAIT),
@@ -694,19 +627,11 @@ class RestClient:
694
627
  build_id = build_id or self.build_id
695
628
  response = self.client.get(f"/v0.1/builds/{build_id}")
696
629
  response.raise_for_status()
697
- except HTTPStatusError as e:
698
- if e.response.status_code in AUTH_ERRORS_TO_RETRY_ON:
699
- raise AuthError(
700
- e.response.status_code,
701
- f"Authentication failed: {e}",
702
- request=e.request,
703
- response=e.response,
704
- ) from e
705
- raise
630
+ except Exception as e:
631
+ raise JobFetchError(f"Error getting build status: {e!s}") from e
706
632
  return response.json()
707
633
 
708
634
  # TODO: Refactor later so we don't have to ignore PLR0915
709
- @refresh_token_on_auth_error()
710
635
  @retry(
711
636
  stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
712
637
  wait=wait_exponential(multiplier=RETRY_MULTIPLIER, max=MAX_RETRY_WAIT),
@@ -886,13 +811,6 @@ class RestClient:
886
811
  build_context = response.json()
887
812
  self.build_id = build_context["build_id"]
888
813
  except HTTPStatusError as e:
889
- if e.response.status_code in AUTH_ERRORS_TO_RETRY_ON:
890
- raise AuthError(
891
- e.response.status_code,
892
- f"Authentication failed: {e}",
893
- request=e.request,
894
- response=e.response,
895
- ) from e
896
814
  error_detail = response.json()
897
815
  error_message = error_detail.get("detail", str(e))
898
816
  raise JobCreationError(
@@ -973,7 +891,6 @@ class RestClient:
973
891
  except Exception as e:
974
892
  raise FileUploadError(f"Error uploading directory {dir_path}: {e}") from e
975
893
 
976
- @refresh_token_on_auth_error()
977
894
  def _upload_single_file(
978
895
  self,
979
896
  job_name: str,
@@ -1047,20 +964,10 @@ class RestClient:
1047
964
  )
1048
965
 
1049
966
  logger.info(f"Successfully uploaded {file_name}")
1050
- except HTTPStatusError as e:
1051
- if e.response.status_code in AUTH_ERRORS_TO_RETRY_ON:
1052
- raise AuthError(
1053
- e.response.status_code,
1054
- f"Authentication failed: {e}",
1055
- request=e.request,
1056
- response=e.response,
1057
- ) from e
1058
- raise
1059
967
  except Exception as e:
1060
968
  logger.exception(f"Error uploading file {file_path}")
1061
969
  raise FileUploadError(f"Error uploading file {file_path}: {e}") from e
1062
970
 
1063
- @refresh_token_on_auth_error()
1064
971
  @retry(
1065
972
  stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
1066
973
  wait=wait_exponential(multiplier=RETRY_MULTIPLIER, max=MAX_RETRY_WAIT),
@@ -1097,13 +1004,6 @@ class RestClient:
1097
1004
  response.raise_for_status()
1098
1005
  return response.json()
1099
1006
  except HTTPStatusError as e:
1100
- if e.response.status_code in AUTH_ERRORS_TO_RETRY_ON:
1101
- raise AuthError(
1102
- e.response.status_code,
1103
- f"Authentication failed: {e}",
1104
- request=e.request,
1105
- response=e.response,
1106
- ) from e
1107
1007
  logger.exception(
1108
1008
  f"Error listing files for job {job_name}, trajectory {trajectory_id}, upload_id {upload_id}: {e.response.text}"
1109
1009
  )
@@ -1116,7 +1016,6 @@ class RestClient:
1116
1016
  )
1117
1017
  raise RestClientError(f"Error listing files: {e!s}") from e
1118
1018
 
1119
- @refresh_token_on_auth_error()
1120
1019
  @retry(
1121
1020
  stop=stop_after_attempt(MAX_RETRY_ATTEMPTS),
1122
1021
  wait=wait_exponential(multiplier=RETRY_MULTIPLIER, max=MAX_RETRY_WAIT),
@@ -1164,13 +1063,6 @@ class RestClient:
1164
1063
 
1165
1064
  logger.info(f"File {file_path} downloaded to {destination_path}")
1166
1065
  except HTTPStatusError as e:
1167
- if e.response.status_code in AUTH_ERRORS_TO_RETRY_ON:
1168
- raise AuthError(
1169
- e.response.status_code,
1170
- f"Authentication failed: {e}",
1171
- request=e.request,
1172
- response=e.response,
1173
- ) from e
1174
1066
  logger.exception(
1175
1067
  f"Error downloading file {file_path} for job {job_name}, trajectory_id {trajectory_id}: {e.response.text}"
1176
1068
  )
@@ -1,107 +1,89 @@
1
- import asyncio
2
1
  import logging
3
- from collections.abc import Callable, Coroutine
4
- from functools import wraps
5
- from typing import Any, Final, Optional, ParamSpec, TypeVar, overload
2
+ from typing import ClassVar, Final
6
3
 
7
4
  import httpx
8
- from httpx import HTTPStatusError
9
5
 
10
- logger = logging.getLogger(__name__)
11
-
12
- T = TypeVar("T")
13
- P = ParamSpec("P")
14
-
15
- AUTH_ERRORS_TO_RETRY_ON: Final[set[int]] = {
16
- httpx.codes.UNAUTHORIZED,
17
- httpx.codes.FORBIDDEN,
18
- }
19
-
20
-
21
- class AuthError(Exception):
22
- """Raised when authentication fails with 401/403 status."""
23
-
24
- def __init__(self, status_code: int, message: str, request=None, response=None):
25
- self.status_code = status_code
26
- self.request = request
27
- self.response = response
28
- super().__init__(message)
29
-
30
-
31
- def is_auth_error(e: Exception) -> bool:
32
- if isinstance(e, AuthError):
33
- return True
34
- if isinstance(e, HTTPStatusError):
35
- return e.response.status_code in AUTH_ERRORS_TO_RETRY_ON
36
- return False
37
-
38
-
39
- def get_status_code(e: Exception) -> Optional[int]:
40
- if isinstance(e, AuthError):
41
- return e.status_code
42
- if isinstance(e, HTTPStatusError):
43
- return e.response.status_code
44
- return None
6
+ from futurehouse_client.models.app import APIKeyPayload, AuthType
45
7
 
8
+ logger = logging.getLogger(__name__)
46
9
 
47
- @overload
48
- def refresh_token_on_auth_error(
49
- func: Callable[P, Coroutine[Any, Any, T]],
50
- ) -> Callable[P, Coroutine[Any, Any, T]]: ...
51
-
52
-
53
- @overload
54
- def refresh_token_on_auth_error(
55
- func: None = None, *, max_retries: int = ...
56
- ) -> Callable[[Callable[P, T]], Callable[P, T]]: ...
57
-
58
-
59
- def refresh_token_on_auth_error(func=None, max_retries=1):
60
- """Decorator that refreshes JWT token on 401/403 auth errors."""
61
-
62
- def decorator(fn):
63
- @wraps(fn)
64
- def sync_wrapper(self, *args, **kwargs):
65
- retries = 0
66
- while True:
67
- try:
68
- return fn(self, *args, **kwargs)
69
- except Exception as e:
70
- if is_auth_error(e) and retries < max_retries:
71
- retries += 1
72
- status = get_status_code(e) or "Unknown"
73
- logger.info(
74
- f"Received auth error {status}, "
75
- f"refreshing token and retrying (attempt {retries}/{max_retries})..."
76
- )
77
- self.auth_jwt = self._run_auth()
78
- self._clients = {}
79
- continue
80
- raise
81
-
82
- @wraps(fn)
83
- async def async_wrapper(self, *args, **kwargs):
84
- retries = 0
85
- while True:
86
- try:
87
- return await fn(self, *args, **kwargs)
88
- except Exception as e:
89
- if is_auth_error(e) and retries < max_retries:
90
- retries += 1
91
- status = get_status_code(e) or "Unknown"
92
- logger.info(
93
- f"Received auth error {status}, "
94
- f"refreshing token and retrying (attempt {retries}/{max_retries})..."
95
- )
96
- self.auth_jwt = self._run_auth()
97
- self._clients = {}
98
- continue
99
- raise
100
-
101
- if asyncio.iscoroutinefunction(fn):
102
- return async_wrapper
103
- return sync_wrapper
104
-
105
- if callable(func):
106
- return decorator(func)
107
- return decorator
10
+ INVALID_REFRESH_TYPE_MSG: Final[str] = (
11
+ "API key auth is required to refresh auth tokens."
12
+ )
13
+ JWT_TOKEN_CACHE_EXPIRY: int = 300 # seconds
14
+
15
+
16
+ def _run_auth(
17
+ client: httpx.Client,
18
+ auth_type: AuthType = AuthType.API_KEY,
19
+ api_key: str | None = None,
20
+ jwt: str | None = None,
21
+ ) -> str:
22
+ auth_payload: APIKeyPayload | None
23
+ if auth_type == AuthType.API_KEY:
24
+ auth_payload = APIKeyPayload(api_key=api_key)
25
+ elif auth_type == AuthType.JWT:
26
+ auth_payload = None
27
+ try:
28
+ if auth_payload:
29
+ response = client.post("/auth/login", json=auth_payload.model_dump())
30
+ response.raise_for_status()
31
+ token_data = response.json()
32
+ elif jwt:
33
+ token_data = {"access_token": jwt, "expires_in": JWT_TOKEN_CACHE_EXPIRY}
34
+ else:
35
+ raise ValueError("JWT token required for JWT authentication.")
36
+
37
+ return token_data["access_token"]
38
+ except Exception as e:
39
+ raise Exception("Failed to authenticate") from e # noqa: TRY002
40
+
41
+
42
+ class RefreshingJWT(httpx.Auth):
43
+ """Automatically (re-)inject a JWT and transparently retry exactly once when we hit a 401/403."""
44
+
45
+ RETRY_STATUSES: ClassVar[set[int]] = {
46
+ httpx.codes.UNAUTHORIZED,
47
+ httpx.codes.FORBIDDEN,
48
+ }
49
+
50
+ def __init__(
51
+ self,
52
+ auth_client: httpx.Client,
53
+ auth_type: AuthType = AuthType.API_KEY,
54
+ api_key: str | None = None,
55
+ jwt: str | None = None,
56
+ ):
57
+ self.auth_type = auth_type
58
+ self.auth_client = auth_client
59
+ self.api_key = api_key
60
+ self._jwt = _run_auth(
61
+ client=auth_client,
62
+ jwt=jwt,
63
+ auth_type=auth_type,
64
+ api_key=api_key,
65
+ )
66
+
67
+ def refresh_token(self):
68
+ if self.auth_type == AuthType.JWT:
69
+ logger.error(INVALID_REFRESH_TYPE_MSG)
70
+ raise ValueError(INVALID_REFRESH_TYPE_MSG)
71
+ self._jwt = _run_auth(
72
+ client=self.auth_client,
73
+ auth_type=self.auth_type,
74
+ api_key=self.api_key,
75
+ )
76
+
77
+ def auth_flow(self, request):
78
+ request.headers["Authorization"] = f"Bearer {self._jwt}"
79
+ response = yield request
80
+
81
+ # If it failed, refresh once and replay the request
82
+ if response.status_code in self.RETRY_STATUSES:
83
+ logger.info(
84
+ "Received %s, refreshing token and retrying …",
85
+ response.status_code,
86
+ )
87
+ self.refresh_token()
88
+ request.headers["Authorization"] = f"Bearer {self._jwt}"
89
+ yield request # second (and final) attempt, again or use a while loop
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: futurehouse-client
3
- Version: 0.3.18.dev185
3
+ Version: 0.3.18.dev195
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
  Classifier: Operating System :: OS Independent
@@ -1,17 +1,17 @@
1
1
  futurehouse_client/__init__.py,sha256=ddxO7JE97c6bt7LjNglZZ2Ql8bYCGI9laSFeh9MP6VU,344
2
2
  futurehouse_client/clients/__init__.py,sha256=tFWqwIAY5PvwfOVsCje4imjTpf6xXNRMh_UHIKVI1_0,320
3
3
  futurehouse_client/clients/job_client.py,sha256=uNkqQbeZw7wbA0qDWcIOwOykrosza-jev58paJZ_mbA,11150
4
- futurehouse_client/clients/rest_client.py,sha256=5lhE7jC9qq6pIERMJeKxhi0s-JDvUjx2iEZYFrWPQmw,47870
4
+ futurehouse_client/clients/rest_client.py,sha256=6HQF3YXDnSdGxAoXpB_wU6Vhcqhp5OB5SNuGQJ6Hseo,43454
5
5
  futurehouse_client/models/__init__.py,sha256=5x-f9AoM1hGzJBEHcHAXSt7tPeImST5oZLuMdwp0mXc,554
6
6
  futurehouse_client/models/app.py,sha256=w_1e4F0IiC-BKeOLqYkABYo4U-Nka1S-F64S_eHB2KM,26421
7
7
  futurehouse_client/models/client.py,sha256=n4HD0KStKLm6Ek9nL9ylP-bkK10yzAaD1uIDF83Qp_A,1828
8
8
  futurehouse_client/models/rest.py,sha256=lgwkMIXz0af-49BYSkKeS7SRqvN3motqnAikDN4YGTc,789
9
9
  futurehouse_client/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
- futurehouse_client/utils/auth.py,sha256=Lq9mjSGc7iuRP6fmLICCS6KjzLHN6-tJUuhYp0XXrkE,3342
10
+ futurehouse_client/utils/auth.py,sha256=0V161S9jW4vbTCoJJrOtNzWXQkAVyzdGM3yefGgJ578,2808
11
11
  futurehouse_client/utils/general.py,sha256=A_rtTiYW30ELGEZlWCIArO7q1nEmqi8hUlmBRYkMQ_c,767
12
12
  futurehouse_client/utils/module_utils.py,sha256=aFyd-X-pDARXz9GWpn8SSViUVYdSbuy9vSkrzcVIaGI,4955
13
13
  futurehouse_client/utils/monitoring.py,sha256=UjRlufe67kI3VxRHOd5fLtJmlCbVA2Wqwpd4uZhXkQM,8728
14
- futurehouse_client-0.3.18.dev185.dist-info/METADATA,sha256=7hPM-HuORaESFdHfo-tttwG1vmJ7zecr9IYMRBOG1xc,12767
15
- futurehouse_client-0.3.18.dev185.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
16
- futurehouse_client-0.3.18.dev185.dist-info/top_level.txt,sha256=TRuLUCt_qBnggdFHCX4O_BoCu1j2X43lKfIZC-ElwWY,19
17
- futurehouse_client-0.3.18.dev185.dist-info/RECORD,,
14
+ futurehouse_client-0.3.18.dev195.dist-info/METADATA,sha256=yM1NbN2au3MmkfIkkuT85eYahKYTmnBuaWCQ1OvQ97A,12767
15
+ futurehouse_client-0.3.18.dev195.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
16
+ futurehouse_client-0.3.18.dev195.dist-info/top_level.txt,sha256=TRuLUCt_qBnggdFHCX4O_BoCu1j2X43lKfIZC-ElwWY,19
17
+ futurehouse_client-0.3.18.dev195.dist-info/RECORD,,