mirascope 2.0.0a1__py3-none-any.whl → 2.0.0a3__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.
Files changed (205) hide show
  1. mirascope/__init__.py +2 -2
  2. mirascope/api/__init__.py +6 -0
  3. mirascope/api/_generated/README.md +207 -0
  4. mirascope/api/_generated/__init__.py +85 -0
  5. mirascope/api/_generated/client.py +155 -0
  6. mirascope/api/_generated/core/__init__.py +52 -0
  7. mirascope/api/_generated/core/api_error.py +23 -0
  8. mirascope/api/_generated/core/client_wrapper.py +58 -0
  9. mirascope/api/_generated/core/datetime_utils.py +30 -0
  10. mirascope/api/_generated/core/file.py +70 -0
  11. mirascope/api/_generated/core/force_multipart.py +16 -0
  12. mirascope/api/_generated/core/http_client.py +619 -0
  13. mirascope/api/_generated/core/http_response.py +55 -0
  14. mirascope/api/_generated/core/jsonable_encoder.py +102 -0
  15. mirascope/api/_generated/core/pydantic_utilities.py +310 -0
  16. mirascope/api/_generated/core/query_encoder.py +60 -0
  17. mirascope/api/_generated/core/remove_none_from_dict.py +11 -0
  18. mirascope/api/_generated/core/request_options.py +35 -0
  19. mirascope/api/_generated/core/serialization.py +282 -0
  20. mirascope/api/_generated/docs/__init__.py +4 -0
  21. mirascope/api/_generated/docs/client.py +95 -0
  22. mirascope/api/_generated/docs/raw_client.py +132 -0
  23. mirascope/api/_generated/environment.py +9 -0
  24. mirascope/api/_generated/errors/__init__.py +7 -0
  25. mirascope/api/_generated/errors/bad_request_error.py +15 -0
  26. mirascope/api/_generated/health/__init__.py +7 -0
  27. mirascope/api/_generated/health/client.py +96 -0
  28. mirascope/api/_generated/health/raw_client.py +129 -0
  29. mirascope/api/_generated/health/types/__init__.py +8 -0
  30. mirascope/api/_generated/health/types/health_check_response.py +24 -0
  31. mirascope/api/_generated/health/types/health_check_response_status.py +5 -0
  32. mirascope/api/_generated/reference.md +167 -0
  33. mirascope/api/_generated/traces/__init__.py +55 -0
  34. mirascope/api/_generated/traces/client.py +162 -0
  35. mirascope/api/_generated/traces/raw_client.py +168 -0
  36. mirascope/api/_generated/traces/types/__init__.py +95 -0
  37. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item.py +36 -0
  38. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource.py +31 -0
  39. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item.py +25 -0
  40. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value.py +54 -0
  41. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_array_value.py +23 -0
  42. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value.py +28 -0
  43. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_resource_attributes_item_value_kvlist_value_values_item.py +24 -0
  44. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item.py +35 -0
  45. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope.py +35 -0
  46. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item.py +27 -0
  47. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value.py +54 -0
  48. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_array_value.py +23 -0
  49. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_kvlist_value.py +28 -0
  50. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_scope_attributes_item_value_kvlist_value_values_item.py +24 -0
  51. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item.py +60 -0
  52. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item.py +29 -0
  53. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value.py +54 -0
  54. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_array_value.py +23 -0
  55. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_kvlist_value.py +28 -0
  56. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_attributes_item_value_kvlist_value_values_item.py +24 -0
  57. mirascope/api/_generated/traces/types/traces_create_request_resource_spans_item_scope_spans_item_spans_item_status.py +24 -0
  58. mirascope/api/_generated/traces/types/traces_create_response.py +27 -0
  59. mirascope/api/_generated/traces/types/traces_create_response_partial_success.py +28 -0
  60. mirascope/api/_generated/types/__init__.py +21 -0
  61. mirascope/api/_generated/types/http_api_decode_error.py +31 -0
  62. mirascope/api/_generated/types/http_api_decode_error_tag.py +5 -0
  63. mirascope/api/_generated/types/issue.py +44 -0
  64. mirascope/api/_generated/types/issue_tag.py +17 -0
  65. mirascope/api/_generated/types/property_key.py +7 -0
  66. mirascope/api/_generated/types/property_key_tag.py +29 -0
  67. mirascope/api/_generated/types/property_key_tag_tag.py +5 -0
  68. mirascope/api/client.py +255 -0
  69. mirascope/api/settings.py +81 -0
  70. mirascope/llm/__init__.py +41 -11
  71. mirascope/llm/calls/calls.py +81 -57
  72. mirascope/llm/calls/decorator.py +121 -115
  73. mirascope/llm/content/__init__.py +3 -2
  74. mirascope/llm/context/_utils.py +19 -6
  75. mirascope/llm/exceptions.py +30 -16
  76. mirascope/llm/formatting/_utils.py +9 -5
  77. mirascope/llm/formatting/format.py +2 -2
  78. mirascope/llm/formatting/from_call_args.py +2 -2
  79. mirascope/llm/messages/message.py +13 -5
  80. mirascope/llm/models/__init__.py +2 -2
  81. mirascope/llm/models/models.py +189 -81
  82. mirascope/llm/prompts/__init__.py +13 -12
  83. mirascope/llm/prompts/_utils.py +27 -24
  84. mirascope/llm/prompts/decorator.py +133 -204
  85. mirascope/llm/prompts/prompts.py +424 -0
  86. mirascope/llm/prompts/protocols.py +25 -59
  87. mirascope/llm/providers/__init__.py +38 -0
  88. mirascope/llm/{clients → providers}/_missing_import_stubs.py +8 -6
  89. mirascope/llm/providers/anthropic/__init__.py +24 -0
  90. mirascope/llm/{clients → providers}/anthropic/_utils/decode.py +5 -4
  91. mirascope/llm/{clients → providers}/anthropic/_utils/encode.py +31 -10
  92. mirascope/llm/providers/anthropic/model_id.py +40 -0
  93. mirascope/llm/{clients/anthropic/clients.py → providers/anthropic/provider.py} +33 -418
  94. mirascope/llm/{clients → providers}/base/__init__.py +3 -3
  95. mirascope/llm/{clients → providers}/base/_utils.py +10 -7
  96. mirascope/llm/{clients/base/client.py → providers/base/base_provider.py} +255 -126
  97. mirascope/llm/providers/google/__init__.py +21 -0
  98. mirascope/llm/{clients → providers}/google/_utils/decode.py +6 -4
  99. mirascope/llm/{clients → providers}/google/_utils/encode.py +30 -24
  100. mirascope/llm/providers/google/model_id.py +28 -0
  101. mirascope/llm/providers/google/provider.py +438 -0
  102. mirascope/llm/providers/load_provider.py +48 -0
  103. mirascope/llm/providers/mlx/__init__.py +24 -0
  104. mirascope/llm/providers/mlx/_utils.py +107 -0
  105. mirascope/llm/providers/mlx/encoding/__init__.py +8 -0
  106. mirascope/llm/providers/mlx/encoding/base.py +69 -0
  107. mirascope/llm/providers/mlx/encoding/transformers.py +131 -0
  108. mirascope/llm/providers/mlx/mlx.py +237 -0
  109. mirascope/llm/providers/mlx/model_id.py +17 -0
  110. mirascope/llm/providers/mlx/provider.py +411 -0
  111. mirascope/llm/providers/model_id.py +16 -0
  112. mirascope/llm/providers/openai/__init__.py +6 -0
  113. mirascope/llm/providers/openai/completions/__init__.py +20 -0
  114. mirascope/llm/{clients/openai/responses → providers/openai/completions}/_utils/__init__.py +2 -0
  115. mirascope/llm/{clients → providers}/openai/completions/_utils/decode.py +5 -3
  116. mirascope/llm/{clients → providers}/openai/completions/_utils/encode.py +33 -23
  117. mirascope/llm/providers/openai/completions/provider.py +456 -0
  118. mirascope/llm/providers/openai/model_id.py +31 -0
  119. mirascope/llm/providers/openai/model_info.py +246 -0
  120. mirascope/llm/providers/openai/provider.py +386 -0
  121. mirascope/llm/providers/openai/responses/__init__.py +21 -0
  122. mirascope/llm/{clients → providers}/openai/responses/_utils/decode.py +5 -3
  123. mirascope/llm/{clients → providers}/openai/responses/_utils/encode.py +28 -17
  124. mirascope/llm/providers/openai/responses/provider.py +470 -0
  125. mirascope/llm/{clients → providers}/openai/shared/_utils.py +7 -3
  126. mirascope/llm/providers/provider_id.py +13 -0
  127. mirascope/llm/providers/provider_registry.py +167 -0
  128. mirascope/llm/responses/base_response.py +10 -5
  129. mirascope/llm/responses/base_stream_response.py +10 -5
  130. mirascope/llm/responses/response.py +24 -13
  131. mirascope/llm/responses/root_response.py +7 -12
  132. mirascope/llm/responses/stream_response.py +35 -23
  133. mirascope/llm/tools/__init__.py +9 -2
  134. mirascope/llm/tools/_utils.py +12 -3
  135. mirascope/llm/tools/decorator.py +10 -10
  136. mirascope/llm/tools/protocols.py +4 -4
  137. mirascope/llm/tools/tool_schema.py +44 -9
  138. mirascope/llm/tools/tools.py +12 -11
  139. mirascope/ops/__init__.py +156 -0
  140. mirascope/ops/_internal/__init__.py +5 -0
  141. mirascope/ops/_internal/closure.py +1118 -0
  142. mirascope/ops/_internal/configuration.py +126 -0
  143. mirascope/ops/_internal/context.py +76 -0
  144. mirascope/ops/_internal/exporters/__init__.py +26 -0
  145. mirascope/ops/_internal/exporters/exporters.py +342 -0
  146. mirascope/ops/_internal/exporters/processors.py +104 -0
  147. mirascope/ops/_internal/exporters/types.py +165 -0
  148. mirascope/ops/_internal/exporters/utils.py +29 -0
  149. mirascope/ops/_internal/instrumentation/__init__.py +8 -0
  150. mirascope/ops/_internal/instrumentation/llm/__init__.py +8 -0
  151. mirascope/ops/_internal/instrumentation/llm/encode.py +238 -0
  152. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/__init__.py +38 -0
  153. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_input_messages.py +31 -0
  154. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_output_messages.py +38 -0
  155. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/gen_ai_system_instructions.py +18 -0
  156. mirascope/ops/_internal/instrumentation/llm/gen_ai_types/shared.py +100 -0
  157. mirascope/ops/_internal/instrumentation/llm/llm.py +1288 -0
  158. mirascope/ops/_internal/propagation.py +198 -0
  159. mirascope/ops/_internal/protocols.py +51 -0
  160. mirascope/ops/_internal/session.py +139 -0
  161. mirascope/ops/_internal/spans.py +232 -0
  162. mirascope/ops/_internal/traced_calls.py +371 -0
  163. mirascope/ops/_internal/traced_functions.py +394 -0
  164. mirascope/ops/_internal/tracing.py +276 -0
  165. mirascope/ops/_internal/types.py +13 -0
  166. mirascope/ops/_internal/utils.py +75 -0
  167. mirascope/ops/_internal/versioned_calls.py +512 -0
  168. mirascope/ops/_internal/versioned_functions.py +346 -0
  169. mirascope/ops/_internal/versioning.py +303 -0
  170. mirascope/ops/exceptions.py +21 -0
  171. {mirascope-2.0.0a1.dist-info → mirascope-2.0.0a3.dist-info}/METADATA +77 -1
  172. mirascope-2.0.0a3.dist-info/RECORD +206 -0
  173. {mirascope-2.0.0a1.dist-info → mirascope-2.0.0a3.dist-info}/WHEEL +1 -1
  174. mirascope/graphs/__init__.py +0 -22
  175. mirascope/graphs/finite_state_machine.py +0 -625
  176. mirascope/llm/agents/__init__.py +0 -15
  177. mirascope/llm/agents/agent.py +0 -97
  178. mirascope/llm/agents/agent_template.py +0 -45
  179. mirascope/llm/agents/decorator.py +0 -176
  180. mirascope/llm/calls/base_call.py +0 -33
  181. mirascope/llm/clients/__init__.py +0 -34
  182. mirascope/llm/clients/anthropic/__init__.py +0 -25
  183. mirascope/llm/clients/anthropic/model_ids.py +0 -8
  184. mirascope/llm/clients/google/__init__.py +0 -20
  185. mirascope/llm/clients/google/clients.py +0 -853
  186. mirascope/llm/clients/google/model_ids.py +0 -15
  187. mirascope/llm/clients/openai/__init__.py +0 -25
  188. mirascope/llm/clients/openai/completions/__init__.py +0 -28
  189. mirascope/llm/clients/openai/completions/_utils/model_features.py +0 -81
  190. mirascope/llm/clients/openai/completions/clients.py +0 -833
  191. mirascope/llm/clients/openai/completions/model_ids.py +0 -8
  192. mirascope/llm/clients/openai/responses/__init__.py +0 -26
  193. mirascope/llm/clients/openai/responses/_utils/model_features.py +0 -87
  194. mirascope/llm/clients/openai/responses/clients.py +0 -832
  195. mirascope/llm/clients/openai/responses/model_ids.py +0 -8
  196. mirascope/llm/clients/providers.py +0 -175
  197. mirascope-2.0.0a1.dist-info/RECORD +0 -102
  198. /mirascope/llm/{clients → providers}/anthropic/_utils/__init__.py +0 -0
  199. /mirascope/llm/{clients → providers}/base/kwargs.py +0 -0
  200. /mirascope/llm/{clients → providers}/base/params.py +0 -0
  201. /mirascope/llm/{clients → providers}/google/_utils/__init__.py +0 -0
  202. /mirascope/llm/{clients → providers}/google/message.py +0 -0
  203. /mirascope/llm/{clients/openai/completions → providers/openai/responses}/_utils/__init__.py +0 -0
  204. /mirascope/llm/{clients → providers}/openai/shared/__init__.py +0 -0
  205. {mirascope-2.0.0a1.dist-info → mirascope-2.0.0a3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,70 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ from typing import IO, Dict, List, Mapping, Optional, Tuple, Union, cast
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 = Union[IO[bytes], bytes, str]
8
+ File = Union[
9
+ # file (or bytes)
10
+ FileContent,
11
+ # (filename, file (or bytes))
12
+ Tuple[Optional[str], FileContent],
13
+ # (filename, file (or bytes), content_type)
14
+ Tuple[Optional[str], FileContent, Optional[str]],
15
+ # (filename, file (or bytes), content_type, headers)
16
+ Tuple[
17
+ Optional[str],
18
+ FileContent,
19
+ Optional[str],
20
+ Mapping[str, str],
21
+ ],
22
+ ]
23
+
24
+
25
+ def convert_file_dict_to_httpx_tuples(
26
+ d: Dict[str, Union[File, List[File]]],
27
+ ) -> List[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
44
+
45
+
46
+ def with_content_type(*, file: File, default_content_type: str) -> File:
47
+ """
48
+ This function resolves to the file's content type, if provided, and defaults
49
+ to the default_content_type value if not.
50
+ """
51
+ if isinstance(file, tuple):
52
+ if len(file) == 2:
53
+ filename, content = cast(Tuple[Optional[str], FileContent], file) # type: ignore
54
+ return (filename, content, default_content_type)
55
+ elif len(file) == 3:
56
+ filename, content, file_content_type = cast(
57
+ Tuple[Optional[str], FileContent, Optional[str]], file
58
+ ) # type: ignore
59
+ out_content_type = file_content_type or default_content_type
60
+ return (filename, content, out_content_type)
61
+ elif len(file) == 4:
62
+ filename, content, file_content_type, headers = cast( # type: ignore
63
+ Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]],
64
+ file,
65
+ )
66
+ out_content_type = file_content_type or default_content_type
67
+ return (filename, content, out_content_type, headers)
68
+ else:
69
+ raise ValueError(f"Unexpected tuple length: {len(file)}")
70
+ return (None, file, default_content_type)
@@ -0,0 +1,16 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+
4
+ class ForceMultipartDict(dict):
5
+ """
6
+ A dictionary subclass that always evaluates to True in boolean contexts.
7
+
8
+ This is used to force multipart/form-data encoding in HTTP requests even when
9
+ the dictionary is empty, which would normally evaluate to False.
10
+ """
11
+
12
+ def __bool__(self):
13
+ return True
14
+
15
+
16
+ FORCE_MULTIPART = ForceMultipartDict()
@@ -0,0 +1,619 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ import asyncio
4
+ import email.utils
5
+ import re
6
+ import time
7
+ import typing
8
+ import urllib.parse
9
+ from contextlib import asynccontextmanager, contextmanager
10
+ from random import random
11
+
12
+ import httpx
13
+ from .file import File, convert_file_dict_to_httpx_tuples
14
+ from .force_multipart import FORCE_MULTIPART
15
+ from .jsonable_encoder import jsonable_encoder
16
+ from .query_encoder import encode_query
17
+ from .remove_none_from_dict import remove_none_from_dict
18
+ from .request_options import RequestOptions
19
+ from httpx._types import RequestFiles
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
+ retryable_400s = [429, 408, 409]
91
+ return response.status_code >= 500 or response.status_code in retryable_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.Callable[[], typing.Optional[float]],
162
+ base_headers: typing.Callable[[], typing.Dict[str, str]],
163
+ base_url: typing.Optional[typing.Callable[[], 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 = maybe_base_url
172
+ if self.base_url is not None and base_url is None:
173
+ base_url = self.base_url()
174
+
175
+ if base_url is None:
176
+ raise ValueError(
177
+ "A base_url is required to make this request, please provide one and try again."
178
+ )
179
+ return base_url
180
+
181
+ def request(
182
+ self,
183
+ path: typing.Optional[str] = None,
184
+ *,
185
+ method: str,
186
+ base_url: typing.Optional[str] = None,
187
+ params: typing.Optional[typing.Dict[str, typing.Any]] = None,
188
+ json: typing.Optional[typing.Any] = None,
189
+ data: typing.Optional[typing.Any] = None,
190
+ content: typing.Optional[
191
+ typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]
192
+ ] = None,
193
+ files: typing.Optional[
194
+ typing.Union[
195
+ typing.Dict[
196
+ str, typing.Optional[typing.Union[File, typing.List[File]]]
197
+ ],
198
+ typing.List[typing.Tuple[str, File]],
199
+ ]
200
+ ] = None,
201
+ headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
202
+ request_options: typing.Optional[RequestOptions] = None,
203
+ retries: int = 2,
204
+ omit: typing.Optional[typing.Any] = None,
205
+ force_multipart: typing.Optional[bool] = None,
206
+ ) -> httpx.Response:
207
+ base_url = self.get_base_url(base_url)
208
+ timeout = (
209
+ request_options.get("timeout_in_seconds")
210
+ if request_options is not None
211
+ and request_options.get("timeout_in_seconds") is not None
212
+ else self.base_timeout()
213
+ )
214
+
215
+ json_body, data_body = get_request_body(
216
+ json=json, data=data, request_options=request_options, omit=omit
217
+ )
218
+
219
+ request_files: typing.Optional[RequestFiles] = (
220
+ convert_file_dict_to_httpx_tuples(
221
+ remove_omit_from_dict(remove_none_from_dict(files), omit)
222
+ )
223
+ if (files is not None and files is not omit and isinstance(files, dict))
224
+ else None
225
+ )
226
+
227
+ if (request_files is None or len(request_files) == 0) and force_multipart:
228
+ request_files = FORCE_MULTIPART
229
+
230
+ response = self.httpx_client.request(
231
+ method=method,
232
+ url=urllib.parse.urljoin(f"{base_url}/", path),
233
+ headers=jsonable_encoder(
234
+ remove_none_from_dict(
235
+ {
236
+ **self.base_headers(),
237
+ **(headers if headers is not None else {}),
238
+ **(
239
+ request_options.get("additional_headers", {}) or {}
240
+ if request_options is not None
241
+ else {}
242
+ ),
243
+ }
244
+ )
245
+ ),
246
+ params=encode_query(
247
+ jsonable_encoder(
248
+ remove_none_from_dict(
249
+ remove_omit_from_dict(
250
+ {
251
+ **(params if params is not None else {}),
252
+ **(
253
+ request_options.get(
254
+ "additional_query_parameters", {}
255
+ )
256
+ or {}
257
+ if request_options is not None
258
+ else {}
259
+ ),
260
+ },
261
+ omit,
262
+ )
263
+ )
264
+ )
265
+ ),
266
+ json=json_body,
267
+ data=data_body,
268
+ content=content,
269
+ files=request_files,
270
+ timeout=timeout,
271
+ )
272
+
273
+ max_retries: int = (
274
+ request_options.get("max_retries", 0) if request_options is not None else 0
275
+ )
276
+ if _should_retry(response=response):
277
+ if max_retries > retries:
278
+ time.sleep(_retry_timeout(response=response, retries=retries))
279
+ return self.request(
280
+ path=path,
281
+ method=method,
282
+ base_url=base_url,
283
+ params=params,
284
+ json=json,
285
+ content=content,
286
+ files=files,
287
+ headers=headers,
288
+ request_options=request_options,
289
+ retries=retries + 1,
290
+ omit=omit,
291
+ )
292
+
293
+ return response
294
+
295
+ @contextmanager
296
+ def stream(
297
+ self,
298
+ path: typing.Optional[str] = None,
299
+ *,
300
+ method: str,
301
+ base_url: typing.Optional[str] = None,
302
+ params: typing.Optional[typing.Dict[str, typing.Any]] = None,
303
+ json: typing.Optional[typing.Any] = None,
304
+ data: typing.Optional[typing.Any] = None,
305
+ content: typing.Optional[
306
+ typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]
307
+ ] = None,
308
+ files: typing.Optional[
309
+ typing.Union[
310
+ typing.Dict[
311
+ str, typing.Optional[typing.Union[File, typing.List[File]]]
312
+ ],
313
+ typing.List[typing.Tuple[str, File]],
314
+ ]
315
+ ] = None,
316
+ headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
317
+ request_options: typing.Optional[RequestOptions] = None,
318
+ retries: int = 2,
319
+ omit: typing.Optional[typing.Any] = None,
320
+ force_multipart: typing.Optional[bool] = None,
321
+ ) -> typing.Iterator[httpx.Response]:
322
+ base_url = self.get_base_url(base_url)
323
+ timeout = (
324
+ request_options.get("timeout_in_seconds")
325
+ if request_options is not None
326
+ and request_options.get("timeout_in_seconds") is not None
327
+ else self.base_timeout()
328
+ )
329
+
330
+ request_files: typing.Optional[RequestFiles] = (
331
+ convert_file_dict_to_httpx_tuples(
332
+ remove_omit_from_dict(remove_none_from_dict(files), omit)
333
+ )
334
+ if (files is not None and files is not omit and isinstance(files, dict))
335
+ else None
336
+ )
337
+
338
+ if (request_files is None or len(request_files) == 0) and force_multipart:
339
+ request_files = FORCE_MULTIPART
340
+
341
+ json_body, data_body = get_request_body(
342
+ json=json, data=data, request_options=request_options, omit=omit
343
+ )
344
+
345
+ with self.httpx_client.stream(
346
+ method=method,
347
+ url=urllib.parse.urljoin(f"{base_url}/", path),
348
+ headers=jsonable_encoder(
349
+ remove_none_from_dict(
350
+ {
351
+ **self.base_headers(),
352
+ **(headers if headers is not None else {}),
353
+ **(
354
+ request_options.get("additional_headers", {})
355
+ if request_options is not None
356
+ else {}
357
+ ),
358
+ }
359
+ )
360
+ ),
361
+ params=encode_query(
362
+ jsonable_encoder(
363
+ remove_none_from_dict(
364
+ remove_omit_from_dict(
365
+ {
366
+ **(params if params is not None else {}),
367
+ **(
368
+ request_options.get(
369
+ "additional_query_parameters", {}
370
+ )
371
+ if request_options is not None
372
+ else {}
373
+ ),
374
+ },
375
+ omit,
376
+ )
377
+ )
378
+ )
379
+ ),
380
+ json=json_body,
381
+ data=data_body,
382
+ content=content,
383
+ files=request_files,
384
+ timeout=timeout,
385
+ ) as stream:
386
+ yield stream
387
+
388
+
389
+ class AsyncHttpClient:
390
+ def __init__(
391
+ self,
392
+ *,
393
+ httpx_client: httpx.AsyncClient,
394
+ base_timeout: typing.Callable[[], typing.Optional[float]],
395
+ base_headers: typing.Callable[[], typing.Dict[str, str]],
396
+ base_url: typing.Optional[typing.Callable[[], str]] = None,
397
+ ):
398
+ self.base_url = base_url
399
+ self.base_timeout = base_timeout
400
+ self.base_headers = base_headers
401
+ self.httpx_client = httpx_client
402
+
403
+ def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str:
404
+ base_url = maybe_base_url
405
+ if self.base_url is not None and base_url is None:
406
+ base_url = self.base_url()
407
+
408
+ if base_url is None:
409
+ raise ValueError(
410
+ "A base_url is required to make this request, please provide one and try again."
411
+ )
412
+ return base_url
413
+
414
+ async def request(
415
+ self,
416
+ path: typing.Optional[str] = None,
417
+ *,
418
+ method: str,
419
+ base_url: typing.Optional[str] = None,
420
+ params: typing.Optional[typing.Dict[str, typing.Any]] = None,
421
+ json: typing.Optional[typing.Any] = None,
422
+ data: typing.Optional[typing.Any] = None,
423
+ content: typing.Optional[
424
+ typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]
425
+ ] = None,
426
+ files: typing.Optional[
427
+ typing.Union[
428
+ typing.Dict[
429
+ str, typing.Optional[typing.Union[File, typing.List[File]]]
430
+ ],
431
+ typing.List[typing.Tuple[str, File]],
432
+ ]
433
+ ] = None,
434
+ headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
435
+ request_options: typing.Optional[RequestOptions] = None,
436
+ retries: int = 2,
437
+ omit: typing.Optional[typing.Any] = None,
438
+ force_multipart: typing.Optional[bool] = None,
439
+ ) -> httpx.Response:
440
+ base_url = self.get_base_url(base_url)
441
+ timeout = (
442
+ request_options.get("timeout_in_seconds")
443
+ if request_options is not None
444
+ and request_options.get("timeout_in_seconds") is not None
445
+ else self.base_timeout()
446
+ )
447
+
448
+ request_files: typing.Optional[RequestFiles] = (
449
+ convert_file_dict_to_httpx_tuples(
450
+ remove_omit_from_dict(remove_none_from_dict(files), omit)
451
+ )
452
+ if (files is not None and files is not omit and isinstance(files, dict))
453
+ else None
454
+ )
455
+
456
+ if (request_files is None or len(request_files) == 0) and force_multipart:
457
+ request_files = FORCE_MULTIPART
458
+
459
+ json_body, data_body = get_request_body(
460
+ json=json, data=data, request_options=request_options, omit=omit
461
+ )
462
+
463
+ # Add the input to each of these and do None-safety checks
464
+ response = await self.httpx_client.request(
465
+ method=method,
466
+ url=urllib.parse.urljoin(f"{base_url}/", path),
467
+ headers=jsonable_encoder(
468
+ remove_none_from_dict(
469
+ {
470
+ **self.base_headers(),
471
+ **(headers if headers is not None else {}),
472
+ **(
473
+ request_options.get("additional_headers", {}) or {}
474
+ if request_options is not None
475
+ else {}
476
+ ),
477
+ }
478
+ )
479
+ ),
480
+ params=encode_query(
481
+ jsonable_encoder(
482
+ remove_none_from_dict(
483
+ remove_omit_from_dict(
484
+ {
485
+ **(params if params is not None else {}),
486
+ **(
487
+ request_options.get(
488
+ "additional_query_parameters", {}
489
+ )
490
+ or {}
491
+ if request_options is not None
492
+ else {}
493
+ ),
494
+ },
495
+ omit,
496
+ )
497
+ )
498
+ )
499
+ ),
500
+ json=json_body,
501
+ data=data_body,
502
+ content=content,
503
+ files=request_files,
504
+ timeout=timeout,
505
+ )
506
+
507
+ max_retries: int = (
508
+ request_options.get("max_retries", 0) if request_options is not None else 0
509
+ )
510
+ if _should_retry(response=response):
511
+ if max_retries > retries:
512
+ await asyncio.sleep(_retry_timeout(response=response, retries=retries))
513
+ return await self.request(
514
+ path=path,
515
+ method=method,
516
+ base_url=base_url,
517
+ params=params,
518
+ json=json,
519
+ content=content,
520
+ files=files,
521
+ headers=headers,
522
+ request_options=request_options,
523
+ retries=retries + 1,
524
+ omit=omit,
525
+ )
526
+ return response
527
+
528
+ @asynccontextmanager
529
+ async def stream(
530
+ self,
531
+ path: typing.Optional[str] = None,
532
+ *,
533
+ method: str,
534
+ base_url: typing.Optional[str] = None,
535
+ params: typing.Optional[typing.Dict[str, typing.Any]] = None,
536
+ json: typing.Optional[typing.Any] = None,
537
+ data: typing.Optional[typing.Any] = None,
538
+ content: typing.Optional[
539
+ typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]
540
+ ] = None,
541
+ files: typing.Optional[
542
+ typing.Union[
543
+ typing.Dict[
544
+ str, typing.Optional[typing.Union[File, typing.List[File]]]
545
+ ],
546
+ typing.List[typing.Tuple[str, File]],
547
+ ]
548
+ ] = None,
549
+ headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
550
+ request_options: typing.Optional[RequestOptions] = None,
551
+ retries: int = 2,
552
+ omit: typing.Optional[typing.Any] = None,
553
+ force_multipart: typing.Optional[bool] = None,
554
+ ) -> typing.AsyncIterator[httpx.Response]:
555
+ base_url = self.get_base_url(base_url)
556
+ timeout = (
557
+ request_options.get("timeout_in_seconds")
558
+ if request_options is not None
559
+ and request_options.get("timeout_in_seconds") is not None
560
+ else self.base_timeout()
561
+ )
562
+
563
+ request_files: typing.Optional[RequestFiles] = (
564
+ convert_file_dict_to_httpx_tuples(
565
+ remove_omit_from_dict(remove_none_from_dict(files), omit)
566
+ )
567
+ if (files is not None and files is not omit and isinstance(files, dict))
568
+ else None
569
+ )
570
+
571
+ if (request_files is None or len(request_files) == 0) and force_multipart:
572
+ request_files = FORCE_MULTIPART
573
+
574
+ json_body, data_body = get_request_body(
575
+ json=json, data=data, request_options=request_options, omit=omit
576
+ )
577
+
578
+ async with self.httpx_client.stream(
579
+ method=method,
580
+ url=urllib.parse.urljoin(f"{base_url}/", path),
581
+ headers=jsonable_encoder(
582
+ remove_none_from_dict(
583
+ {
584
+ **self.base_headers(),
585
+ **(headers if headers is not None else {}),
586
+ **(
587
+ request_options.get("additional_headers", {})
588
+ if request_options is not None
589
+ else {}
590
+ ),
591
+ }
592
+ )
593
+ ),
594
+ params=encode_query(
595
+ jsonable_encoder(
596
+ remove_none_from_dict(
597
+ remove_omit_from_dict(
598
+ {
599
+ **(params if params is not None else {}),
600
+ **(
601
+ request_options.get(
602
+ "additional_query_parameters", {}
603
+ )
604
+ if request_options is not None
605
+ else {}
606
+ ),
607
+ },
608
+ omit=omit,
609
+ )
610
+ )
611
+ )
612
+ ),
613
+ json=json_body,
614
+ data=data_body,
615
+ content=content,
616
+ files=request_files,
617
+ timeout=timeout,
618
+ ) as stream:
619
+ yield stream