agenta 0.24.1a0__py3-none-any.whl → 0.24.2a1__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 agenta might be problematic. Click here for more details.

Files changed (132) hide show
  1. agenta/cli/variant_commands.py +15 -10
  2. agenta/client/Readme.md +72 -64
  3. agenta/client/api.py +1 -1
  4. agenta/client/backend/__init__.py +14 -9
  5. agenta/client/backend/apps/client.py +1669 -0
  6. agenta/client/backend/bases/client.py +190 -0
  7. agenta/client/backend/client.py +2102 -868
  8. agenta/client/backend/configs/client.py +598 -0
  9. agenta/client/backend/containers/client.py +638 -0
  10. agenta/client/backend/{resources/containers → containers}/types/container_templates_response.py +1 -2
  11. agenta/client/backend/core/__init__.py +29 -0
  12. agenta/client/backend/core/client_wrapper.py +42 -9
  13. agenta/client/backend/core/datetime_utils.py +1 -1
  14. agenta/client/backend/core/file.py +43 -0
  15. agenta/client/backend/core/http_client.py +553 -0
  16. agenta/client/backend/core/jsonable_encoder.py +33 -39
  17. agenta/client/backend/core/pydantic_utilities.py +212 -0
  18. agenta/client/backend/core/query_encoder.py +60 -0
  19. agenta/client/backend/core/remove_none_from_dict.py +2 -2
  20. agenta/client/backend/core/request_options.py +32 -0
  21. agenta/client/backend/core/serialization.py +179 -0
  22. agenta/client/backend/environments/client.py +190 -0
  23. agenta/client/backend/evaluations/client.py +1462 -0
  24. agenta/client/backend/evaluators/client.py +911 -0
  25. agenta/client/backend/observability/client.py +1271 -0
  26. agenta/client/backend/testsets/client.py +1132 -0
  27. agenta/client/backend/types/__init__.py +8 -6
  28. agenta/client/backend/types/aggregated_result.py +14 -29
  29. agenta/client/backend/types/aggregated_result_evaluator_config.py +1 -2
  30. agenta/client/backend/types/app.py +13 -28
  31. agenta/client/backend/types/app_variant_response.py +21 -37
  32. agenta/client/backend/types/app_variant_revision.py +17 -32
  33. agenta/client/backend/types/base_output.py +13 -28
  34. agenta/client/backend/types/body_import_testset.py +16 -31
  35. agenta/client/backend/types/config_db.py +16 -31
  36. agenta/client/backend/types/correct_answer.py +22 -0
  37. agenta/client/backend/types/create_app_output.py +13 -28
  38. agenta/client/backend/types/create_span.py +33 -50
  39. agenta/client/backend/types/create_trace_response.py +16 -31
  40. agenta/client/backend/types/docker_env_vars.py +13 -28
  41. agenta/client/backend/types/environment_output.py +21 -36
  42. agenta/client/backend/types/environment_output_extended.py +21 -36
  43. agenta/client/backend/types/environment_revision.py +18 -33
  44. agenta/client/backend/types/error.py +16 -31
  45. agenta/client/backend/types/evaluation.py +20 -34
  46. agenta/client/backend/types/evaluation_scenario.py +18 -33
  47. agenta/client/backend/types/evaluation_scenario_input.py +16 -31
  48. agenta/client/backend/types/evaluation_scenario_output.py +18 -33
  49. agenta/client/backend/types/evaluation_scenario_result.py +14 -29
  50. agenta/client/backend/types/evaluation_scenario_score_update.py +13 -28
  51. agenta/client/backend/types/evaluation_status_enum.py +11 -33
  52. agenta/client/backend/types/evaluation_type.py +3 -21
  53. agenta/client/backend/types/evaluator.py +18 -32
  54. agenta/client/backend/types/evaluator_config.py +20 -33
  55. agenta/client/backend/types/get_config_response.py +16 -31
  56. agenta/client/backend/types/http_validation_error.py +14 -29
  57. agenta/client/backend/types/human_evaluation.py +17 -32
  58. agenta/client/backend/types/human_evaluation_scenario.py +21 -37
  59. agenta/client/backend/types/human_evaluation_scenario_input.py +13 -28
  60. agenta/client/backend/types/human_evaluation_scenario_output.py +13 -28
  61. agenta/client/backend/types/human_evaluation_scenario_update.py +26 -41
  62. agenta/client/backend/types/human_evaluation_update.py +14 -29
  63. agenta/client/backend/types/image.py +18 -33
  64. agenta/client/backend/types/invite_request.py +13 -28
  65. agenta/client/backend/types/list_api_keys_response.py +18 -33
  66. agenta/client/backend/types/llm_run_rate_limit.py +13 -28
  67. agenta/client/backend/types/llm_tokens.py +16 -31
  68. agenta/client/backend/types/lm_providers_enum.py +21 -0
  69. agenta/client/backend/types/new_human_evaluation.py +13 -28
  70. agenta/client/backend/types/new_testset.py +16 -31
  71. agenta/client/backend/types/organization.py +22 -36
  72. agenta/client/backend/types/organization_output.py +13 -28
  73. agenta/client/backend/types/outputs.py +5 -0
  74. agenta/client/backend/types/permission.py +36 -137
  75. agenta/client/backend/types/result.py +17 -32
  76. agenta/client/backend/types/simple_evaluation_output.py +13 -28
  77. agenta/client/backend/types/span.py +23 -38
  78. agenta/client/backend/types/span_detail.py +26 -40
  79. agenta/client/backend/types/span_status_code.py +1 -25
  80. agenta/client/backend/types/span_variant.py +16 -31
  81. agenta/client/backend/types/template.py +14 -29
  82. agenta/client/backend/types/template_image_info.py +21 -35
  83. agenta/client/backend/types/test_set_output_response.py +16 -32
  84. agenta/client/backend/types/test_set_simple_response.py +13 -28
  85. agenta/client/backend/types/trace_detail.py +26 -40
  86. agenta/client/backend/types/update_app_output.py +22 -0
  87. agenta/client/backend/types/uri.py +13 -28
  88. agenta/client/backend/types/validation_error.py +13 -28
  89. agenta/client/backend/types/variant_action.py +14 -29
  90. agenta/client/backend/types/variant_action_enum.py +1 -19
  91. agenta/client/backend/types/with_pagination.py +14 -30
  92. agenta/client/backend/types/workspace_member_response.py +14 -29
  93. agenta/client/backend/types/workspace_permission.py +18 -33
  94. agenta/client/backend/types/workspace_response.py +20 -35
  95. agenta/client/backend/types/workspace_role.py +11 -37
  96. agenta/client/backend/types/workspace_role_response.py +17 -32
  97. agenta/client/backend/variants/client.py +1447 -0
  98. agenta/client/backend/variants/types/add_variant_from_base_and_config_response.py +8 -0
  99. agenta/sdk/decorators/llm_entrypoint.py +8 -13
  100. agenta/sdk/tracing/llm_tracing.py +1 -1
  101. {agenta-0.24.1a0.dist-info → agenta-0.24.2a1.dist-info}/METADATA +1 -1
  102. agenta-0.24.2a1.dist-info/RECORD +175 -0
  103. agenta/client/backend/resources/__init__.py +0 -31
  104. agenta/client/backend/resources/apps/client.py +0 -977
  105. agenta/client/backend/resources/bases/client.py +0 -127
  106. agenta/client/backend/resources/configs/client.py +0 -377
  107. agenta/client/backend/resources/containers/client.py +0 -383
  108. agenta/client/backend/resources/environments/client.py +0 -131
  109. agenta/client/backend/resources/evaluations/client.py +0 -1008
  110. agenta/client/backend/resources/evaluators/client.py +0 -594
  111. agenta/client/backend/resources/observability/client.py +0 -1187
  112. agenta/client/backend/resources/testsets/client.py +0 -689
  113. agenta/client/backend/resources/variants/client.py +0 -796
  114. agenta/client/backend/resources/variants/types/add_variant_from_base_and_config_response.py +0 -7
  115. agenta/client/backend/types/evaluation_webhook.py +0 -36
  116. agenta/client/backend/types/feedback.py +0 -40
  117. agenta/client/backend/types/span_kind.py +0 -49
  118. agenta-0.24.1a0.dist-info/RECORD +0 -169
  119. /agenta/client/backend/{resources/apps → apps}/__init__.py +0 -0
  120. /agenta/client/backend/{resources/bases → bases}/__init__.py +0 -0
  121. /agenta/client/backend/{resources/configs → configs}/__init__.py +0 -0
  122. /agenta/client/backend/{resources/containers → containers}/__init__.py +0 -0
  123. /agenta/client/backend/{resources/containers → containers}/types/__init__.py +0 -0
  124. /agenta/client/backend/{resources/environments → environments}/__init__.py +0 -0
  125. /agenta/client/backend/{resources/evaluations → evaluations}/__init__.py +0 -0
  126. /agenta/client/backend/{resources/evaluators → evaluators}/__init__.py +0 -0
  127. /agenta/client/backend/{resources/observability → observability}/__init__.py +0 -0
  128. /agenta/client/backend/{resources/testsets → testsets}/__init__.py +0 -0
  129. /agenta/client/backend/{resources/variants → variants}/__init__.py +0 -0
  130. /agenta/client/backend/{resources/variants → variants}/types/__init__.py +0 -0
  131. {agenta-0.24.1a0.dist-info → agenta-0.24.2a1.dist-info}/WHEEL +0 -0
  132. {agenta-0.24.1a0.dist-info → agenta-0.24.2a1.dist-info}/entry_points.txt +0 -0
@@ -1,31 +1,64 @@
1
1
  # This file was auto-generated by Fern from our API Definition.
2
2
 
3
3
  import typing
4
-
5
4
  import httpx
5
+ from .http_client import HttpClient
6
+ from .http_client import AsyncHttpClient
6
7
 
7
8
 
8
9
  class BaseClientWrapper:
9
- def __init__(self, *, api_key: str, base_url: str):
10
+ def __init__(
11
+ self, *, api_key: str, base_url: str, timeout: typing.Optional[float] = None
12
+ ):
10
13
  self.api_key = api_key
11
14
  self._base_url = base_url
15
+ self._timeout = timeout
12
16
 
13
17
  def get_headers(self) -> typing.Dict[str, str]:
14
- headers: typing.Dict[str, str] = {"X-Fern-Language": "Python"}
18
+ headers: typing.Dict[str, str] = {
19
+ "X-Fern-Language": "Python",
20
+ }
15
21
  headers["Authorization"] = self.api_key
16
22
  return headers
17
23
 
18
24
  def get_base_url(self) -> str:
19
25
  return self._base_url
20
26
 
27
+ def get_timeout(self) -> typing.Optional[float]:
28
+ return self._timeout
29
+
21
30
 
22
31
  class SyncClientWrapper(BaseClientWrapper):
23
- def __init__(self, *, api_key: str, base_url: str, httpx_client: httpx.Client):
24
- super().__init__(api_key=api_key, base_url=base_url)
25
- self.httpx_client = httpx_client
32
+ def __init__(
33
+ self,
34
+ *,
35
+ api_key: str,
36
+ base_url: str,
37
+ timeout: typing.Optional[float] = None,
38
+ httpx_client: httpx.Client,
39
+ ):
40
+ super().__init__(api_key=api_key, base_url=base_url, timeout=timeout)
41
+ self.httpx_client = HttpClient(
42
+ httpx_client=httpx_client,
43
+ base_headers=self.get_headers(),
44
+ base_timeout=self.get_timeout(),
45
+ base_url=self.get_base_url(),
46
+ )
26
47
 
27
48
 
28
49
  class AsyncClientWrapper(BaseClientWrapper):
29
- def __init__(self, *, api_key: str, base_url: str, httpx_client: httpx.AsyncClient):
30
- super().__init__(api_key=api_key, base_url=base_url)
31
- self.httpx_client = httpx_client
50
+ def __init__(
51
+ self,
52
+ *,
53
+ api_key: str,
54
+ base_url: str,
55
+ timeout: typing.Optional[float] = None,
56
+ httpx_client: httpx.AsyncClient,
57
+ ):
58
+ super().__init__(api_key=api_key, base_url=base_url, timeout=timeout)
59
+ self.httpx_client = AsyncHttpClient(
60
+ httpx_client=httpx_client,
61
+ base_headers=self.get_headers(),
62
+ base_timeout=self.get_timeout(),
63
+ base_url=self.get_base_url(),
64
+ )
@@ -25,6 +25,6 @@ def serialize_datetime(v: dt.datetime) -> str:
25
25
  if v.tzinfo is not None:
26
26
  return _serialize_zoned_datetime(v)
27
27
  else:
28
- local_tz = dt.datetime.now(dt.timezone.utc).astimezone().tzinfo
28
+ local_tz = dt.datetime.now().astimezone().tzinfo
29
29
  localized_dt = v.replace(tzinfo=local_tz)
30
30
  return _serialize_zoned_datetime(localized_dt)
@@ -0,0 +1,43 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ import typing
4
+
5
+ # File typing inspired by the flexibility of types within the httpx library
6
+ # https://github.com/encode/httpx/blob/master/httpx/_types.py
7
+ FileContent = typing.Union[typing.IO[bytes], bytes, str]
8
+ File = typing.Union[
9
+ # file (or bytes)
10
+ FileContent,
11
+ # (filename, file (or bytes))
12
+ typing.Tuple[typing.Optional[str], FileContent],
13
+ # (filename, file (or bytes), content_type)
14
+ typing.Tuple[typing.Optional[str], FileContent, typing.Optional[str]],
15
+ # (filename, file (or bytes), content_type, headers)
16
+ typing.Tuple[
17
+ typing.Optional[str],
18
+ FileContent,
19
+ typing.Optional[str],
20
+ typing.Mapping[str, str],
21
+ ],
22
+ ]
23
+
24
+
25
+ def convert_file_dict_to_httpx_tuples(
26
+ d: typing.Dict[str, typing.Union[File, typing.List[File]]],
27
+ ) -> typing.List[typing.Tuple[str, File]]:
28
+ """
29
+ The format we use is a list of tuples, where the first element is the
30
+ name of the file and the second is the file object. Typically HTTPX wants
31
+ a dict, but to be able to send lists of files, you have to use the list
32
+ approach (which also works for non-lists)
33
+ https://github.com/encode/httpx/pull/1032
34
+ """
35
+
36
+ httpx_tuples = []
37
+ for key, file_like in d.items():
38
+ if isinstance(file_like, list):
39
+ for file_like_item in file_like:
40
+ httpx_tuples.append((key, file_like_item))
41
+ else:
42
+ httpx_tuples.append((key, file_like))
43
+ return httpx_tuples
@@ -0,0 +1,553 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ import asyncio
4
+ import email.utils
5
+ import json
6
+ import re
7
+ import time
8
+ import typing
9
+ import urllib.parse
10
+ from contextlib import asynccontextmanager, contextmanager
11
+ from random import random
12
+
13
+ import httpx
14
+
15
+ from .file import File, convert_file_dict_to_httpx_tuples
16
+ from .jsonable_encoder import jsonable_encoder
17
+ from .query_encoder import encode_query
18
+ from .remove_none_from_dict import remove_none_from_dict
19
+ from .request_options import RequestOptions
20
+
21
+ INITIAL_RETRY_DELAY_SECONDS = 0.5
22
+ MAX_RETRY_DELAY_SECONDS = 10
23
+ MAX_RETRY_DELAY_SECONDS_FROM_HEADER = 30
24
+
25
+
26
+ def _parse_retry_after(response_headers: httpx.Headers) -> typing.Optional[float]:
27
+ """
28
+ This function parses the `Retry-After` header in a HTTP response and returns the number of seconds to wait.
29
+
30
+ Inspired by the urllib3 retry implementation.
31
+ """
32
+ retry_after_ms = response_headers.get("retry-after-ms")
33
+ if retry_after_ms is not None:
34
+ try:
35
+ return int(retry_after_ms) / 1000 if retry_after_ms > 0 else 0
36
+ except Exception:
37
+ pass
38
+
39
+ retry_after = response_headers.get("retry-after")
40
+ if retry_after is None:
41
+ return None
42
+
43
+ # Attempt to parse the header as an int.
44
+ if re.match(r"^\s*[0-9]+\s*$", retry_after):
45
+ seconds = float(retry_after)
46
+ # Fallback to parsing it as a date.
47
+ else:
48
+ retry_date_tuple = email.utils.parsedate_tz(retry_after)
49
+ if retry_date_tuple is None:
50
+ return None
51
+ if retry_date_tuple[9] is None: # Python 2
52
+ # Assume UTC if no timezone was specified
53
+ # On Python2.7, parsedate_tz returns None for a timezone offset
54
+ # instead of 0 if no timezone is given, where mktime_tz treats
55
+ # a None timezone offset as local time.
56
+ retry_date_tuple = retry_date_tuple[:9] + (0,) + retry_date_tuple[10:]
57
+
58
+ retry_date = email.utils.mktime_tz(retry_date_tuple)
59
+ seconds = retry_date - time.time()
60
+
61
+ if seconds < 0:
62
+ seconds = 0
63
+
64
+ return seconds
65
+
66
+
67
+ def _retry_timeout(response: httpx.Response, retries: int) -> float:
68
+ """
69
+ Determine the amount of time to wait before retrying a request.
70
+ This function begins by trying to parse a retry-after header from the response, and then proceeds to use exponential backoff
71
+ with a jitter to determine the number of seconds to wait.
72
+ """
73
+
74
+ # If the API asks us to wait a certain amount of time (and it's a reasonable amount), just do what it says.
75
+ retry_after = _parse_retry_after(response.headers)
76
+ if retry_after is not None and retry_after <= MAX_RETRY_DELAY_SECONDS_FROM_HEADER:
77
+ return retry_after
78
+
79
+ # Apply exponential backoff, capped at MAX_RETRY_DELAY_SECONDS.
80
+ retry_delay = min(
81
+ INITIAL_RETRY_DELAY_SECONDS * pow(2.0, retries), MAX_RETRY_DELAY_SECONDS
82
+ )
83
+
84
+ # Add a randomness / jitter to the retry delay to avoid overwhelming the server with retries.
85
+ timeout = retry_delay * (1 - 0.25 * random())
86
+ return timeout if timeout >= 0 else 0
87
+
88
+
89
+ def _should_retry(response: httpx.Response) -> bool:
90
+ retriable_400s = [429, 408, 409]
91
+ return response.status_code >= 500 or response.status_code in retriable_400s
92
+
93
+
94
+ def remove_omit_from_dict(
95
+ original: typing.Dict[str, typing.Optional[typing.Any]],
96
+ omit: typing.Optional[typing.Any],
97
+ ) -> typing.Dict[str, typing.Any]:
98
+ if omit is None:
99
+ return original
100
+ new: typing.Dict[str, typing.Any] = {}
101
+ for key, value in original.items():
102
+ if value is not omit:
103
+ new[key] = value
104
+ return new
105
+
106
+
107
+ def maybe_filter_request_body(
108
+ data: typing.Optional[typing.Any],
109
+ request_options: typing.Optional[RequestOptions],
110
+ omit: typing.Optional[typing.Any],
111
+ ) -> typing.Optional[typing.Any]:
112
+ if data is None:
113
+ return (
114
+ jsonable_encoder(request_options.get("additional_body_parameters", {}))
115
+ or {}
116
+ if request_options is not None
117
+ else None
118
+ )
119
+ elif not isinstance(data, typing.Mapping):
120
+ data_content = jsonable_encoder(data)
121
+ else:
122
+ data_content = {
123
+ **(jsonable_encoder(remove_omit_from_dict(data, omit))), # type: ignore
124
+ **(
125
+ jsonable_encoder(request_options.get("additional_body_parameters", {}))
126
+ or {}
127
+ if request_options is not None
128
+ else {}
129
+ ),
130
+ }
131
+ return data_content
132
+
133
+
134
+ # Abstracted out for testing purposes
135
+ def get_request_body(
136
+ *,
137
+ json: typing.Optional[typing.Any],
138
+ data: typing.Optional[typing.Any],
139
+ request_options: typing.Optional[RequestOptions],
140
+ omit: typing.Optional[typing.Any],
141
+ ) -> typing.Tuple[typing.Optional[typing.Any], typing.Optional[typing.Any]]:
142
+ json_body = None
143
+ data_body = None
144
+ if data is not None:
145
+ data_body = maybe_filter_request_body(data, request_options, omit)
146
+ else:
147
+ # If both data and json are None, we send json data in the event extra properties are specified
148
+ json_body = maybe_filter_request_body(json, request_options, omit)
149
+
150
+ # If you have an empty JSON body, you should just send None
151
+ return (
152
+ json_body if json_body != {} else None
153
+ ), data_body if data_body != {} else None
154
+
155
+
156
+ class HttpClient:
157
+ def __init__(
158
+ self,
159
+ *,
160
+ httpx_client: httpx.Client,
161
+ base_timeout: typing.Optional[float],
162
+ base_headers: typing.Dict[str, str],
163
+ base_url: typing.Optional[str] = None,
164
+ ):
165
+ self.base_url = base_url
166
+ self.base_timeout = base_timeout
167
+ self.base_headers = base_headers
168
+ self.httpx_client = httpx_client
169
+
170
+ def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str:
171
+ base_url = self.base_url if maybe_base_url is None else maybe_base_url
172
+ if base_url is None:
173
+ raise ValueError(
174
+ "A base_url is required to make this request, please provide one and try again."
175
+ )
176
+ return base_url
177
+
178
+ def request(
179
+ self,
180
+ path: typing.Optional[str] = None,
181
+ *,
182
+ method: str,
183
+ base_url: typing.Optional[str] = None,
184
+ params: typing.Optional[typing.Dict[str, typing.Any]] = None,
185
+ json: typing.Optional[typing.Any] = None,
186
+ data: typing.Optional[typing.Any] = None,
187
+ content: typing.Optional[
188
+ typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]
189
+ ] = None,
190
+ files: typing.Optional[
191
+ typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]
192
+ ] = None,
193
+ headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
194
+ request_options: typing.Optional[RequestOptions] = None,
195
+ retries: int = 0,
196
+ omit: typing.Optional[typing.Any] = None,
197
+ ) -> httpx.Response:
198
+ base_url = self.get_base_url(base_url)
199
+ timeout = (
200
+ request_options.get("timeout_in_seconds")
201
+ if request_options is not None
202
+ and request_options.get("timeout_in_seconds") is not None
203
+ else self.base_timeout
204
+ )
205
+
206
+ json_body, data_body = get_request_body(
207
+ json=json, data=data, request_options=request_options, omit=omit
208
+ )
209
+
210
+ response = self.httpx_client.request(
211
+ method=method,
212
+ url=urllib.parse.urljoin(f"{base_url}/", path),
213
+ headers=jsonable_encoder(
214
+ remove_none_from_dict(
215
+ {
216
+ **self.base_headers,
217
+ **(headers if headers is not None else {}),
218
+ **(
219
+ request_options.get("additional_headers", {}) or {}
220
+ if request_options is not None
221
+ else {}
222
+ ),
223
+ }
224
+ )
225
+ ),
226
+ params=encode_query(
227
+ jsonable_encoder(
228
+ remove_none_from_dict(
229
+ remove_omit_from_dict(
230
+ {
231
+ **(params if params is not None else {}),
232
+ **(
233
+ request_options.get(
234
+ "additional_query_parameters", {}
235
+ )
236
+ or {}
237
+ if request_options is not None
238
+ else {}
239
+ ),
240
+ },
241
+ omit,
242
+ )
243
+ )
244
+ )
245
+ ),
246
+ json=json_body,
247
+ data=data_body,
248
+ content=content,
249
+ files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files))
250
+ if files is not None
251
+ else None,
252
+ timeout=timeout,
253
+ )
254
+
255
+ max_retries: int = (
256
+ request_options.get("max_retries", 0) if request_options is not None else 0
257
+ )
258
+ if _should_retry(response=response):
259
+ if max_retries > retries:
260
+ time.sleep(_retry_timeout(response=response, retries=retries))
261
+ return self.request(
262
+ path=path,
263
+ method=method,
264
+ base_url=base_url,
265
+ params=params,
266
+ json=json,
267
+ content=content,
268
+ files=files,
269
+ headers=headers,
270
+ request_options=request_options,
271
+ retries=retries + 1,
272
+ omit=omit,
273
+ )
274
+
275
+ return response
276
+
277
+ @contextmanager
278
+ def stream(
279
+ self,
280
+ path: typing.Optional[str] = None,
281
+ *,
282
+ method: str,
283
+ base_url: typing.Optional[str] = None,
284
+ params: typing.Optional[typing.Dict[str, typing.Any]] = None,
285
+ json: typing.Optional[typing.Any] = None,
286
+ data: typing.Optional[typing.Any] = None,
287
+ content: typing.Optional[
288
+ typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]
289
+ ] = None,
290
+ files: typing.Optional[
291
+ typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]
292
+ ] = None,
293
+ headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
294
+ request_options: typing.Optional[RequestOptions] = None,
295
+ retries: int = 0,
296
+ omit: typing.Optional[typing.Any] = None,
297
+ ) -> typing.Iterator[httpx.Response]:
298
+ base_url = self.get_base_url(base_url)
299
+ timeout = (
300
+ request_options.get("timeout_in_seconds")
301
+ if request_options is not None
302
+ and request_options.get("timeout_in_seconds") is not None
303
+ else self.base_timeout
304
+ )
305
+
306
+ json_body, data_body = get_request_body(
307
+ json=json, data=data, request_options=request_options, omit=omit
308
+ )
309
+
310
+ with self.httpx_client.stream(
311
+ method=method,
312
+ url=urllib.parse.urljoin(f"{base_url}/", path),
313
+ headers=jsonable_encoder(
314
+ remove_none_from_dict(
315
+ {
316
+ **self.base_headers,
317
+ **(headers if headers is not None else {}),
318
+ **(
319
+ request_options.get("additional_headers", {})
320
+ if request_options is not None
321
+ else {}
322
+ ),
323
+ }
324
+ )
325
+ ),
326
+ params=encode_query(
327
+ jsonable_encoder(
328
+ remove_none_from_dict(
329
+ remove_omit_from_dict(
330
+ {
331
+ **(params if params is not None else {}),
332
+ **(
333
+ request_options.get(
334
+ "additional_query_parameters", {}
335
+ )
336
+ if request_options is not None
337
+ else {}
338
+ ),
339
+ },
340
+ omit,
341
+ )
342
+ )
343
+ )
344
+ ),
345
+ json=json_body,
346
+ data=data_body,
347
+ content=content,
348
+ files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files))
349
+ if files is not None
350
+ else None,
351
+ timeout=timeout,
352
+ ) as stream:
353
+ yield stream
354
+
355
+
356
+ class AsyncHttpClient:
357
+ def __init__(
358
+ self,
359
+ *,
360
+ httpx_client: httpx.AsyncClient,
361
+ base_timeout: typing.Optional[float],
362
+ base_headers: typing.Dict[str, str],
363
+ base_url: typing.Optional[str] = None,
364
+ ):
365
+ self.base_url = base_url
366
+ self.base_timeout = base_timeout
367
+ self.base_headers = base_headers
368
+ self.httpx_client = httpx_client
369
+
370
+ def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str:
371
+ base_url = self.base_url if maybe_base_url is None else maybe_base_url
372
+ if base_url is None:
373
+ raise ValueError(
374
+ "A base_url is required to make this request, please provide one and try again."
375
+ )
376
+ return base_url
377
+
378
+ async def request(
379
+ self,
380
+ path: typing.Optional[str] = None,
381
+ *,
382
+ method: str,
383
+ base_url: typing.Optional[str] = None,
384
+ params: typing.Optional[typing.Dict[str, typing.Any]] = None,
385
+ json: typing.Optional[typing.Any] = None,
386
+ data: typing.Optional[typing.Any] = None,
387
+ content: typing.Optional[
388
+ typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]
389
+ ] = None,
390
+ files: typing.Optional[
391
+ typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]
392
+ ] = None,
393
+ headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
394
+ request_options: typing.Optional[RequestOptions] = None,
395
+ retries: int = 0,
396
+ omit: typing.Optional[typing.Any] = None,
397
+ ) -> httpx.Response:
398
+ base_url = self.get_base_url(base_url)
399
+ timeout = (
400
+ request_options.get("timeout_in_seconds")
401
+ if request_options is not None
402
+ and request_options.get("timeout_in_seconds") is not None
403
+ else self.base_timeout
404
+ )
405
+
406
+ json_body, data_body = get_request_body(
407
+ json=json, data=data, request_options=request_options, omit=omit
408
+ )
409
+
410
+ # Add the input to each of these and do None-safety checks
411
+ response = await self.httpx_client.request(
412
+ method=method,
413
+ url=urllib.parse.urljoin(f"{base_url}/", path),
414
+ headers=jsonable_encoder(
415
+ remove_none_from_dict(
416
+ {
417
+ **self.base_headers,
418
+ **(headers if headers is not None else {}),
419
+ **(
420
+ request_options.get("additional_headers", {}) or {}
421
+ if request_options is not None
422
+ else {}
423
+ ),
424
+ }
425
+ )
426
+ ),
427
+ params=encode_query(
428
+ jsonable_encoder(
429
+ remove_none_from_dict(
430
+ remove_omit_from_dict(
431
+ {
432
+ **(params if params is not None else {}),
433
+ **(
434
+ request_options.get(
435
+ "additional_query_parameters", {}
436
+ )
437
+ or {}
438
+ if request_options is not None
439
+ else {}
440
+ ),
441
+ },
442
+ omit,
443
+ )
444
+ )
445
+ )
446
+ ),
447
+ json=json_body,
448
+ data=data_body,
449
+ content=content,
450
+ files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files))
451
+ if files is not None
452
+ else None,
453
+ timeout=timeout,
454
+ )
455
+
456
+ max_retries: int = (
457
+ request_options.get("max_retries", 0) if request_options is not None else 0
458
+ )
459
+ if _should_retry(response=response):
460
+ if max_retries > retries:
461
+ await asyncio.sleep(_retry_timeout(response=response, retries=retries))
462
+ return await self.request(
463
+ path=path,
464
+ method=method,
465
+ base_url=base_url,
466
+ params=params,
467
+ json=json,
468
+ content=content,
469
+ files=files,
470
+ headers=headers,
471
+ request_options=request_options,
472
+ retries=retries + 1,
473
+ omit=omit,
474
+ )
475
+ return response
476
+
477
+ @asynccontextmanager
478
+ async def stream(
479
+ self,
480
+ path: typing.Optional[str] = None,
481
+ *,
482
+ method: str,
483
+ base_url: typing.Optional[str] = None,
484
+ params: typing.Optional[typing.Dict[str, typing.Any]] = None,
485
+ json: typing.Optional[typing.Any] = None,
486
+ data: typing.Optional[typing.Any] = None,
487
+ content: typing.Optional[
488
+ typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]
489
+ ] = None,
490
+ files: typing.Optional[
491
+ typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]
492
+ ] = None,
493
+ headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
494
+ request_options: typing.Optional[RequestOptions] = None,
495
+ retries: int = 0,
496
+ omit: typing.Optional[typing.Any] = None,
497
+ ) -> typing.AsyncIterator[httpx.Response]:
498
+ base_url = self.get_base_url(base_url)
499
+ timeout = (
500
+ request_options.get("timeout_in_seconds")
501
+ if request_options is not None
502
+ and request_options.get("timeout_in_seconds") is not None
503
+ else self.base_timeout
504
+ )
505
+
506
+ json_body, data_body = get_request_body(
507
+ json=json, data=data, request_options=request_options, omit=omit
508
+ )
509
+
510
+ async with self.httpx_client.stream(
511
+ method=method,
512
+ url=urllib.parse.urljoin(f"{base_url}/", path),
513
+ headers=jsonable_encoder(
514
+ remove_none_from_dict(
515
+ {
516
+ **self.base_headers,
517
+ **(headers if headers is not None else {}),
518
+ **(
519
+ request_options.get("additional_headers", {})
520
+ if request_options is not None
521
+ else {}
522
+ ),
523
+ }
524
+ )
525
+ ),
526
+ params=encode_query(
527
+ jsonable_encoder(
528
+ remove_none_from_dict(
529
+ remove_omit_from_dict(
530
+ {
531
+ **(params if params is not None else {}),
532
+ **(
533
+ request_options.get(
534
+ "additional_query_parameters", {}
535
+ )
536
+ if request_options is not None
537
+ else {}
538
+ ),
539
+ },
540
+ omit=omit,
541
+ )
542
+ )
543
+ )
544
+ ),
545
+ json=json_body,
546
+ data=data_body,
547
+ content=content,
548
+ files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files))
549
+ if files is not None
550
+ else None,
551
+ timeout=timeout,
552
+ ) as stream:
553
+ yield stream