athena-intelligence 0.1.127__py3-none-any.whl → 0.1.185__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 (99) hide show
  1. athena/__init__.py +24 -3
  2. athena/agents/__init__.py +2 -0
  3. athena/agents/client.py +51 -95
  4. athena/agents/drive/__init__.py +2 -0
  5. athena/agents/drive/client.py +31 -80
  6. athena/agents/drive/raw_client.py +155 -0
  7. athena/agents/general/__init__.py +2 -0
  8. athena/agents/general/client.py +91 -238
  9. athena/agents/general/raw_client.py +369 -0
  10. athena/agents/raw_client.py +176 -0
  11. athena/agents/research/__init__.py +2 -0
  12. athena/agents/research/client.py +31 -80
  13. athena/agents/research/raw_client.py +155 -0
  14. athena/agents/sql/__init__.py +2 -0
  15. athena/agents/sql/client.py +31 -80
  16. athena/agents/sql/raw_client.py +155 -0
  17. athena/assets/__init__.py +4 -0
  18. athena/assets/client.py +144 -0
  19. athena/assets/raw_client.py +164 -0
  20. athena/base_client.py +25 -11
  21. athena/client.py +1 -1
  22. athena/core/__init__.py +5 -0
  23. athena/core/api_error.py +13 -5
  24. athena/core/client_wrapper.py +33 -8
  25. athena/core/force_multipart.py +16 -0
  26. athena/core/http_client.py +70 -26
  27. athena/core/http_response.py +55 -0
  28. athena/core/jsonable_encoder.py +0 -1
  29. athena/core/pydantic_utilities.py +70 -111
  30. athena/core/serialization.py +7 -3
  31. athena/errors/__init__.py +2 -0
  32. athena/errors/bad_request_error.py +4 -2
  33. athena/errors/content_too_large_error.py +4 -2
  34. athena/errors/internal_server_error.py +4 -3
  35. athena/errors/not_found_error.py +4 -2
  36. athena/errors/unauthorized_error.py +4 -3
  37. athena/errors/unprocessable_entity_error.py +4 -3
  38. athena/errors/unsupported_media_type_error.py +4 -2
  39. athena/query/__init__.py +2 -0
  40. athena/query/client.py +39 -219
  41. athena/query/raw_client.py +344 -0
  42. athena/query/types/__init__.py +2 -0
  43. athena/tools/__init__.py +2 -0
  44. athena/tools/calendar/__init__.py +2 -0
  45. athena/tools/calendar/client.py +35 -79
  46. athena/tools/calendar/raw_client.py +172 -0
  47. athena/tools/client.py +114 -876
  48. athena/tools/email/__init__.py +2 -0
  49. athena/tools/email/client.py +39 -115
  50. athena/tools/email/raw_client.py +248 -0
  51. athena/tools/raw_client.py +1328 -0
  52. athena/tools/structured_data_extractor/__init__.py +2 -0
  53. athena/tools/structured_data_extractor/client.py +42 -96
  54. athena/tools/structured_data_extractor/raw_client.py +240 -0
  55. athena/tools/tasks/__init__.py +2 -0
  56. athena/tools/tasks/client.py +31 -43
  57. athena/tools/tasks/raw_client.py +96 -0
  58. athena/tools/types/__init__.py +2 -0
  59. athena/types/__init__.py +24 -2
  60. athena/types/asset_content_request_out.py +3 -3
  61. athena/types/asset_node.py +3 -3
  62. athena/types/asset_not_found_error.py +2 -2
  63. athena/types/asset_screenshot_response_out.py +4 -4
  64. athena/types/chunk.py +3 -3
  65. athena/types/chunk_content_item.py +3 -2
  66. athena/types/chunk_result.py +3 -3
  67. athena/types/content.py +7 -0
  68. athena/types/custom_agent_response.py +2 -2
  69. athena/types/data_frame_request_out.py +3 -3
  70. athena/types/data_frame_unknown_format_error.py +2 -2
  71. athena/types/document_chunk.py +2 -2
  72. athena/types/drive_agent_response.py +2 -2
  73. athena/types/file_chunk_request_out.py +3 -3
  74. athena/types/file_too_large_error.py +2 -2
  75. athena/types/folder_response.py +10 -4
  76. athena/types/general_agent_config.py +3 -3
  77. athena/types/general_agent_config_enabled_tools_item.py +1 -2
  78. athena/types/general_agent_request.py +15 -4
  79. athena/types/general_agent_response.py +4 -3
  80. athena/types/general_agent_response_message.py +100 -0
  81. athena/types/general_agent_response_message_kwargs.py +74 -0
  82. athena/types/{tool.py → id.py} +1 -1
  83. athena/types/image_url_content.py +2 -2
  84. athena/types/input_message.py +29 -0
  85. athena/types/input_message_content_item.py +39 -0
  86. athena/types/paginated_assets_out.py +52 -0
  87. athena/types/parent_folder_error.py +2 -2
  88. athena/types/prompt_message.py +3 -3
  89. athena/types/public_asset_out.py +97 -0
  90. athena/types/research_agent_response.py +2 -2
  91. athena/types/save_asset_request_out.py +2 -2
  92. athena/types/sql_agent_response.py +2 -2
  93. athena/types/structured_data_extractor_response.py +3 -3
  94. athena/types/text_content.py +2 -2
  95. athena/types/type.py +1 -1
  96. {athena_intelligence-0.1.127.dist-info → athena_intelligence-0.1.185.dist-info}/METADATA +3 -7
  97. athena_intelligence-0.1.185.dist-info/RECORD +112 -0
  98. {athena_intelligence-0.1.127.dist-info → athena_intelligence-0.1.185.dist-info}/WHEEL +1 -1
  99. athena_intelligence-0.1.127.dist-info/RECORD +0 -89
@@ -0,0 +1,164 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ import typing
4
+ from json.decoder import JSONDecodeError
5
+
6
+ from ..core.api_error import ApiError
7
+ from ..core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
8
+ from ..core.http_response import AsyncHttpResponse, HttpResponse
9
+ from ..core.pydantic_utilities import parse_obj_as
10
+ from ..core.request_options import RequestOptions
11
+ from ..errors.unprocessable_entity_error import UnprocessableEntityError
12
+ from ..types.paginated_assets_out import PaginatedAssetsOut
13
+
14
+
15
+ class RawAssetsClient:
16
+ def __init__(self, *, client_wrapper: SyncClientWrapper):
17
+ self._client_wrapper = client_wrapper
18
+
19
+ def list(
20
+ self,
21
+ *,
22
+ limit: typing.Optional[int] = None,
23
+ offset: typing.Optional[int] = None,
24
+ filters: typing.Optional[str] = None,
25
+ sort: typing.Optional[str] = None,
26
+ request_options: typing.Optional[RequestOptions] = None,
27
+ ) -> HttpResponse[PaginatedAssetsOut]:
28
+ """
29
+ Retrieve a paginated list of assets with optional filtering and sorting. Assets include documents, presentations, spreadsheets, images, videos, and other file types managed by Athena Intelligence.
30
+
31
+ Parameters
32
+ ----------
33
+ limit : typing.Optional[int]
34
+ Maximum number of assets to return per page (1-500)
35
+
36
+ offset : typing.Optional[int]
37
+ Number of assets to skip for pagination
38
+
39
+ filters : typing.Optional[str]
40
+ JSON string of filter criteria. Supports: created_by_id, created_by_email, tags, created_after/before, updated_after/before, title_substring, is_archived, is_hidden, athena_metadata, media_type, athena_converted_type, athena_original_type, summary_ready, summary_status
41
+
42
+ sort : typing.Optional[str]
43
+ JSON string of sort criteria: [{"field": "updated_at", "direction": "desc"}]. Supported fields: created_by_id, created_by_email, created_at, updated_at, is_archived, is_hidden, summary_ready, summary_status
44
+
45
+ request_options : typing.Optional[RequestOptions]
46
+ Request-specific configuration.
47
+
48
+ Returns
49
+ -------
50
+ HttpResponse[PaginatedAssetsOut]
51
+ Successfully retrieved paginated list of assets
52
+ """
53
+ _response = self._client_wrapper.httpx_client.request(
54
+ "api/v0/assets",
55
+ method="GET",
56
+ params={
57
+ "limit": limit,
58
+ "offset": offset,
59
+ "filters": filters,
60
+ "sort": sort,
61
+ },
62
+ request_options=request_options,
63
+ )
64
+ try:
65
+ if 200 <= _response.status_code < 300:
66
+ _data = typing.cast(
67
+ PaginatedAssetsOut,
68
+ parse_obj_as(
69
+ type_=PaginatedAssetsOut, # type: ignore
70
+ object_=_response.json(),
71
+ ),
72
+ )
73
+ return HttpResponse(response=_response, data=_data)
74
+ if _response.status_code == 422:
75
+ raise UnprocessableEntityError(
76
+ headers=dict(_response.headers),
77
+ body=typing.cast(
78
+ typing.Optional[typing.Any],
79
+ parse_obj_as(
80
+ type_=typing.Optional[typing.Any], # type: ignore
81
+ object_=_response.json(),
82
+ ),
83
+ ),
84
+ )
85
+ _response_json = _response.json()
86
+ except JSONDecodeError:
87
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
88
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
89
+
90
+
91
+ class AsyncRawAssetsClient:
92
+ def __init__(self, *, client_wrapper: AsyncClientWrapper):
93
+ self._client_wrapper = client_wrapper
94
+
95
+ async def list(
96
+ self,
97
+ *,
98
+ limit: typing.Optional[int] = None,
99
+ offset: typing.Optional[int] = None,
100
+ filters: typing.Optional[str] = None,
101
+ sort: typing.Optional[str] = None,
102
+ request_options: typing.Optional[RequestOptions] = None,
103
+ ) -> AsyncHttpResponse[PaginatedAssetsOut]:
104
+ """
105
+ Retrieve a paginated list of assets with optional filtering and sorting. Assets include documents, presentations, spreadsheets, images, videos, and other file types managed by Athena Intelligence.
106
+
107
+ Parameters
108
+ ----------
109
+ limit : typing.Optional[int]
110
+ Maximum number of assets to return per page (1-500)
111
+
112
+ offset : typing.Optional[int]
113
+ Number of assets to skip for pagination
114
+
115
+ filters : typing.Optional[str]
116
+ JSON string of filter criteria. Supports: created_by_id, created_by_email, tags, created_after/before, updated_after/before, title_substring, is_archived, is_hidden, athena_metadata, media_type, athena_converted_type, athena_original_type, summary_ready, summary_status
117
+
118
+ sort : typing.Optional[str]
119
+ JSON string of sort criteria: [{"field": "updated_at", "direction": "desc"}]. Supported fields: created_by_id, created_by_email, created_at, updated_at, is_archived, is_hidden, summary_ready, summary_status
120
+
121
+ request_options : typing.Optional[RequestOptions]
122
+ Request-specific configuration.
123
+
124
+ Returns
125
+ -------
126
+ AsyncHttpResponse[PaginatedAssetsOut]
127
+ Successfully retrieved paginated list of assets
128
+ """
129
+ _response = await self._client_wrapper.httpx_client.request(
130
+ "api/v0/assets",
131
+ method="GET",
132
+ params={
133
+ "limit": limit,
134
+ "offset": offset,
135
+ "filters": filters,
136
+ "sort": sort,
137
+ },
138
+ request_options=request_options,
139
+ )
140
+ try:
141
+ if 200 <= _response.status_code < 300:
142
+ _data = typing.cast(
143
+ PaginatedAssetsOut,
144
+ parse_obj_as(
145
+ type_=PaginatedAssetsOut, # type: ignore
146
+ object_=_response.json(),
147
+ ),
148
+ )
149
+ return AsyncHttpResponse(response=_response, data=_data)
150
+ if _response.status_code == 422:
151
+ raise UnprocessableEntityError(
152
+ headers=dict(_response.headers),
153
+ body=typing.cast(
154
+ typing.Optional[typing.Any],
155
+ parse_obj_as(
156
+ type_=typing.Optional[typing.Any], # type: ignore
157
+ object_=_response.json(),
158
+ ),
159
+ ),
160
+ )
161
+ _response_json = _response.json()
162
+ except JSONDecodeError:
163
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response.text)
164
+ raise ApiError(status_code=_response.status_code, headers=dict(_response.headers), body=_response_json)
athena/base_client.py CHANGED
@@ -1,16 +1,14 @@
1
1
  # This file was auto-generated by Fern from our API Definition.
2
2
 
3
3
  import typing
4
- from .environment import AthenaEnvironment
4
+
5
5
  import httpx
6
- from .core.client_wrapper import SyncClientWrapper
7
- from .agents.client import AgentsClient
8
- from .query.client import QueryClient
9
- from .tools.client import ToolsClient
10
- from .core.client_wrapper import AsyncClientWrapper
11
- from .agents.client import AsyncAgentsClient
12
- from .query.client import AsyncQueryClient
13
- from .tools.client import AsyncToolsClient
6
+ from .agents.client import AgentsClient, AsyncAgentsClient
7
+ from .assets.client import AssetsClient, AsyncAssetsClient
8
+ from .core.client_wrapper import AsyncClientWrapper, SyncClientWrapper
9
+ from .environment import AthenaEnvironment
10
+ from .query.client import AsyncQueryClient, QueryClient
11
+ from .tools.client import AsyncToolsClient, ToolsClient
14
12
 
15
13
 
16
14
  class BaseAthena:
@@ -32,6 +30,9 @@ class BaseAthena:
32
30
 
33
31
 
34
32
  api_key : str
33
+ headers : typing.Optional[typing.Dict[str, str]]
34
+ Additional headers to send with every request.
35
+
35
36
  timeout : typing.Optional[float]
36
37
  The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced.
37
38
 
@@ -56,14 +57,18 @@ class BaseAthena:
56
57
  base_url: typing.Optional[str] = None,
57
58
  environment: AthenaEnvironment = AthenaEnvironment.PRODUCTION,
58
59
  api_key: str,
60
+ headers: typing.Optional[typing.Dict[str, str]] = None,
59
61
  timeout: typing.Optional[float] = None,
60
62
  follow_redirects: typing.Optional[bool] = True,
61
63
  httpx_client: typing.Optional[httpx.Client] = None,
62
64
  ):
63
- _defaulted_timeout = timeout if timeout is not None else 60 if httpx_client is None else None
65
+ _defaulted_timeout = (
66
+ timeout if timeout is not None else 60 if httpx_client is None else httpx_client.timeout.read
67
+ )
64
68
  self._client_wrapper = SyncClientWrapper(
65
69
  base_url=_get_base_url(base_url=base_url, environment=environment),
66
70
  api_key=api_key,
71
+ headers=headers,
67
72
  httpx_client=httpx_client
68
73
  if httpx_client is not None
69
74
  else httpx.Client(timeout=_defaulted_timeout, follow_redirects=follow_redirects)
@@ -72,6 +77,7 @@ class BaseAthena:
72
77
  timeout=_defaulted_timeout,
73
78
  )
74
79
  self.agents = AgentsClient(client_wrapper=self._client_wrapper)
80
+ self.assets = AssetsClient(client_wrapper=self._client_wrapper)
75
81
  self.query = QueryClient(client_wrapper=self._client_wrapper)
76
82
  self.tools = ToolsClient(client_wrapper=self._client_wrapper)
77
83
 
@@ -95,6 +101,9 @@ class AsyncBaseAthena:
95
101
 
96
102
 
97
103
  api_key : str
104
+ headers : typing.Optional[typing.Dict[str, str]]
105
+ Additional headers to send with every request.
106
+
98
107
  timeout : typing.Optional[float]
99
108
  The timeout to be used, in seconds, for requests. By default the timeout is 60 seconds, unless a custom httpx client is used, in which case this default is not enforced.
100
109
 
@@ -119,14 +128,18 @@ class AsyncBaseAthena:
119
128
  base_url: typing.Optional[str] = None,
120
129
  environment: AthenaEnvironment = AthenaEnvironment.PRODUCTION,
121
130
  api_key: str,
131
+ headers: typing.Optional[typing.Dict[str, str]] = None,
122
132
  timeout: typing.Optional[float] = None,
123
133
  follow_redirects: typing.Optional[bool] = True,
124
134
  httpx_client: typing.Optional[httpx.AsyncClient] = None,
125
135
  ):
126
- _defaulted_timeout = timeout if timeout is not None else 60 if httpx_client is None else None
136
+ _defaulted_timeout = (
137
+ timeout if timeout is not None else 60 if httpx_client is None else httpx_client.timeout.read
138
+ )
127
139
  self._client_wrapper = AsyncClientWrapper(
128
140
  base_url=_get_base_url(base_url=base_url, environment=environment),
129
141
  api_key=api_key,
142
+ headers=headers,
130
143
  httpx_client=httpx_client
131
144
  if httpx_client is not None
132
145
  else httpx.AsyncClient(timeout=_defaulted_timeout, follow_redirects=follow_redirects)
@@ -135,6 +148,7 @@ class AsyncBaseAthena:
135
148
  timeout=_defaulted_timeout,
136
149
  )
137
150
  self.agents = AsyncAgentsClient(client_wrapper=self._client_wrapper)
151
+ self.assets = AsyncAssetsClient(client_wrapper=self._client_wrapper)
138
152
  self.query = AsyncQueryClient(client_wrapper=self._client_wrapper)
139
153
  self.tools = AsyncToolsClient(client_wrapper=self._client_wrapper)
140
154
 
athena/client.py CHANGED
@@ -169,7 +169,7 @@ class WrappedToolsClient(ToolsClient):
169
169
  Gets the file togehter with media type returned by server
170
170
  """
171
171
  # while we wait for https://github.com/fern-api/fern/issues/4316
172
- result = self._client_wrapper.httpx_client.request(
172
+ result = self.with_raw_response._client_wrapper.httpx_client.request(
173
173
  "api/v0/tools/file/raw-data", method="GET", params={"asset_id": asset_id}
174
174
  )
175
175
  if result.status_code != 200:
athena/core/__init__.py CHANGED
@@ -1,10 +1,13 @@
1
1
  # This file was auto-generated by Fern from our API Definition.
2
2
 
3
+ # isort: skip_file
4
+
3
5
  from .api_error import ApiError
4
6
  from .client_wrapper import AsyncClientWrapper, BaseClientWrapper, SyncClientWrapper
5
7
  from .datetime_utils import serialize_datetime
6
8
  from .file import File, convert_file_dict_to_httpx_tuples, with_content_type
7
9
  from .http_client import AsyncHttpClient, HttpClient
10
+ from .http_response import AsyncHttpResponse, HttpResponse
8
11
  from .jsonable_encoder import jsonable_encoder
9
12
  from .pydantic_utilities import (
10
13
  IS_PYDANTIC_V2,
@@ -24,10 +27,12 @@ __all__ = [
24
27
  "ApiError",
25
28
  "AsyncClientWrapper",
26
29
  "AsyncHttpClient",
30
+ "AsyncHttpResponse",
27
31
  "BaseClientWrapper",
28
32
  "FieldMetadata",
29
33
  "File",
30
34
  "HttpClient",
35
+ "HttpResponse",
31
36
  "IS_PYDANTIC_V2",
32
37
  "RequestOptions",
33
38
  "SyncClientWrapper",
athena/core/api_error.py CHANGED
@@ -1,15 +1,23 @@
1
1
  # This file was auto-generated by Fern from our API Definition.
2
2
 
3
- import typing
3
+ from typing import Any, Dict, Optional
4
4
 
5
5
 
6
6
  class ApiError(Exception):
7
- status_code: typing.Optional[int]
8
- body: typing.Any
7
+ headers: Optional[Dict[str, str]]
8
+ status_code: Optional[int]
9
+ body: Any
9
10
 
10
- def __init__(self, *, status_code: typing.Optional[int] = None, body: typing.Any = None):
11
+ def __init__(
12
+ self,
13
+ *,
14
+ headers: Optional[Dict[str, str]] = None,
15
+ status_code: Optional[int] = None,
16
+ body: Any = None,
17
+ ) -> None:
18
+ self.headers = headers
11
19
  self.status_code = status_code
12
20
  self.body = body
13
21
 
14
22
  def __str__(self) -> str:
15
- return f"status_code: {self.status_code}, body: {self.body}"
23
+ return f"headers: {self.headers}, status_code: {self.status_code}, body: {self.body}"
@@ -1,26 +1,39 @@
1
1
  # This file was auto-generated by Fern from our API Definition.
2
2
 
3
3
  import typing
4
+
4
5
  import httpx
5
- from .http_client import HttpClient
6
- from .http_client import AsyncHttpClient
6
+ from .http_client import AsyncHttpClient, HttpClient
7
7
 
8
8
 
9
9
  class BaseClientWrapper:
10
- def __init__(self, *, api_key: str, base_url: str, timeout: typing.Optional[float] = None):
10
+ def __init__(
11
+ self,
12
+ *,
13
+ api_key: str,
14
+ headers: typing.Optional[typing.Dict[str, str]] = None,
15
+ base_url: str,
16
+ timeout: typing.Optional[float] = None,
17
+ ):
11
18
  self.api_key = api_key
19
+ self._headers = headers
12
20
  self._base_url = base_url
13
21
  self._timeout = timeout
14
22
 
15
23
  def get_headers(self) -> typing.Dict[str, str]:
16
24
  headers: typing.Dict[str, str] = {
25
+ "User-Agent": "athena-intelligence/0.1.185",
17
26
  "X-Fern-Language": "Python",
18
27
  "X-Fern-SDK-Name": "athena-intelligence",
19
- "X-Fern-SDK-Version": "0.1.127",
28
+ "X-Fern-SDK-Version": "0.1.185",
29
+ **(self.get_custom_headers() or {}),
20
30
  }
21
31
  headers["X-API-KEY"] = self.api_key
22
32
  return headers
23
33
 
34
+ def get_custom_headers(self) -> typing.Optional[typing.Dict[str, str]]:
35
+ return self._headers
36
+
24
37
  def get_base_url(self) -> str:
25
38
  return self._base_url
26
39
 
@@ -30,9 +43,15 @@ class BaseClientWrapper:
30
43
 
31
44
  class SyncClientWrapper(BaseClientWrapper):
32
45
  def __init__(
33
- self, *, api_key: str, base_url: str, timeout: typing.Optional[float] = None, httpx_client: httpx.Client
46
+ self,
47
+ *,
48
+ api_key: str,
49
+ headers: typing.Optional[typing.Dict[str, str]] = None,
50
+ base_url: str,
51
+ timeout: typing.Optional[float] = None,
52
+ httpx_client: httpx.Client,
34
53
  ):
35
- super().__init__(api_key=api_key, base_url=base_url, timeout=timeout)
54
+ super().__init__(api_key=api_key, headers=headers, base_url=base_url, timeout=timeout)
36
55
  self.httpx_client = HttpClient(
37
56
  httpx_client=httpx_client,
38
57
  base_headers=self.get_headers,
@@ -43,9 +62,15 @@ class SyncClientWrapper(BaseClientWrapper):
43
62
 
44
63
  class AsyncClientWrapper(BaseClientWrapper):
45
64
  def __init__(
46
- self, *, api_key: str, base_url: str, timeout: typing.Optional[float] = None, httpx_client: httpx.AsyncClient
65
+ self,
66
+ *,
67
+ api_key: str,
68
+ headers: typing.Optional[typing.Dict[str, str]] = None,
69
+ base_url: str,
70
+ timeout: typing.Optional[float] = None,
71
+ httpx_client: httpx.AsyncClient,
47
72
  ):
48
- super().__init__(api_key=api_key, base_url=base_url, timeout=timeout)
73
+ super().__init__(api_key=api_key, headers=headers, base_url=base_url, timeout=timeout)
49
74
  self.httpx_client = AsyncHttpClient(
50
75
  httpx_client=httpx_client,
51
76
  base_headers=self.get_headers,
@@ -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()
@@ -2,7 +2,6 @@
2
2
 
3
3
  import asyncio
4
4
  import email.utils
5
- import json
6
5
  import re
7
6
  import time
8
7
  import typing
@@ -11,12 +10,13 @@ from contextlib import asynccontextmanager, contextmanager
11
10
  from random import random
12
11
 
13
12
  import httpx
14
-
15
13
  from .file import File, convert_file_dict_to_httpx_tuples
14
+ from .force_multipart import FORCE_MULTIPART
16
15
  from .jsonable_encoder import jsonable_encoder
17
16
  from .query_encoder import encode_query
18
17
  from .remove_none_from_dict import remove_none_from_dict
19
18
  from .request_options import RequestOptions
19
+ from httpx._types import RequestFiles
20
20
 
21
21
  INITIAL_RETRY_DELAY_SECONDS = 0.5
22
22
  MAX_RETRY_DELAY_SECONDS = 10
@@ -180,11 +180,17 @@ class HttpClient:
180
180
  json: typing.Optional[typing.Any] = None,
181
181
  data: typing.Optional[typing.Any] = None,
182
182
  content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None,
183
- files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None,
183
+ files: typing.Optional[
184
+ typing.Union[
185
+ typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]],
186
+ typing.List[typing.Tuple[str, File]],
187
+ ]
188
+ ] = None,
184
189
  headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
185
190
  request_options: typing.Optional[RequestOptions] = None,
186
191
  retries: int = 2,
187
192
  omit: typing.Optional[typing.Any] = None,
193
+ force_multipart: typing.Optional[bool] = None,
188
194
  ) -> httpx.Response:
189
195
  base_url = self.get_base_url(base_url)
190
196
  timeout = (
@@ -195,6 +201,15 @@ class HttpClient:
195
201
 
196
202
  json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit)
197
203
 
204
+ request_files: typing.Optional[RequestFiles] = (
205
+ convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit))
206
+ if (files is not None and files is not omit and isinstance(files, dict))
207
+ else None
208
+ )
209
+
210
+ if (request_files is None or len(request_files) == 0) and force_multipart:
211
+ request_files = FORCE_MULTIPART
212
+
198
213
  response = self.httpx_client.request(
199
214
  method=method,
200
215
  url=urllib.parse.urljoin(f"{base_url}/", path),
@@ -227,11 +242,7 @@ class HttpClient:
227
242
  json=json_body,
228
243
  data=data_body,
229
244
  content=content,
230
- files=(
231
- convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit))
232
- if (files is not None and files is not omit)
233
- else None
234
- ),
245
+ files=request_files,
235
246
  timeout=timeout,
236
247
  )
237
248
 
@@ -266,11 +277,17 @@ class HttpClient:
266
277
  json: typing.Optional[typing.Any] = None,
267
278
  data: typing.Optional[typing.Any] = None,
268
279
  content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None,
269
- files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None,
280
+ files: typing.Optional[
281
+ typing.Union[
282
+ typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]],
283
+ typing.List[typing.Tuple[str, File]],
284
+ ]
285
+ ] = None,
270
286
  headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
271
287
  request_options: typing.Optional[RequestOptions] = None,
272
288
  retries: int = 2,
273
289
  omit: typing.Optional[typing.Any] = None,
290
+ force_multipart: typing.Optional[bool] = None,
274
291
  ) -> typing.Iterator[httpx.Response]:
275
292
  base_url = self.get_base_url(base_url)
276
293
  timeout = (
@@ -279,6 +296,15 @@ class HttpClient:
279
296
  else self.base_timeout()
280
297
  )
281
298
 
299
+ request_files: typing.Optional[RequestFiles] = (
300
+ convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit))
301
+ if (files is not None and files is not omit and isinstance(files, dict))
302
+ else None
303
+ )
304
+
305
+ if (request_files is None or len(request_files) == 0) and force_multipart:
306
+ request_files = FORCE_MULTIPART
307
+
282
308
  json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit)
283
309
 
284
310
  with self.httpx_client.stream(
@@ -313,11 +339,7 @@ class HttpClient:
313
339
  json=json_body,
314
340
  data=data_body,
315
341
  content=content,
316
- files=(
317
- convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit))
318
- if (files is not None and files is not omit)
319
- else None
320
- ),
342
+ files=request_files,
321
343
  timeout=timeout,
322
344
  ) as stream:
323
345
  yield stream
@@ -356,11 +378,17 @@ class AsyncHttpClient:
356
378
  json: typing.Optional[typing.Any] = None,
357
379
  data: typing.Optional[typing.Any] = None,
358
380
  content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None,
359
- files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None,
381
+ files: typing.Optional[
382
+ typing.Union[
383
+ typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]],
384
+ typing.List[typing.Tuple[str, File]],
385
+ ]
386
+ ] = None,
360
387
  headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
361
388
  request_options: typing.Optional[RequestOptions] = None,
362
389
  retries: int = 2,
363
390
  omit: typing.Optional[typing.Any] = None,
391
+ force_multipart: typing.Optional[bool] = None,
364
392
  ) -> httpx.Response:
365
393
  base_url = self.get_base_url(base_url)
366
394
  timeout = (
@@ -369,6 +397,15 @@ class AsyncHttpClient:
369
397
  else self.base_timeout()
370
398
  )
371
399
 
400
+ request_files: typing.Optional[RequestFiles] = (
401
+ convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit))
402
+ if (files is not None and files is not omit and isinstance(files, dict))
403
+ else None
404
+ )
405
+
406
+ if (request_files is None or len(request_files) == 0) and force_multipart:
407
+ request_files = FORCE_MULTIPART
408
+
372
409
  json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit)
373
410
 
374
411
  # Add the input to each of these and do None-safety checks
@@ -404,11 +441,7 @@ class AsyncHttpClient:
404
441
  json=json_body,
405
442
  data=data_body,
406
443
  content=content,
407
- files=(
408
- convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit))
409
- if files is not None
410
- else None
411
- ),
444
+ files=request_files,
412
445
  timeout=timeout,
413
446
  )
414
447
 
@@ -442,11 +475,17 @@ class AsyncHttpClient:
442
475
  json: typing.Optional[typing.Any] = None,
443
476
  data: typing.Optional[typing.Any] = None,
444
477
  content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None,
445
- files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None,
478
+ files: typing.Optional[
479
+ typing.Union[
480
+ typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]],
481
+ typing.List[typing.Tuple[str, File]],
482
+ ]
483
+ ] = None,
446
484
  headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
447
485
  request_options: typing.Optional[RequestOptions] = None,
448
486
  retries: int = 2,
449
487
  omit: typing.Optional[typing.Any] = None,
488
+ force_multipart: typing.Optional[bool] = None,
450
489
  ) -> typing.AsyncIterator[httpx.Response]:
451
490
  base_url = self.get_base_url(base_url)
452
491
  timeout = (
@@ -455,6 +494,15 @@ class AsyncHttpClient:
455
494
  else self.base_timeout()
456
495
  )
457
496
 
497
+ request_files: typing.Optional[RequestFiles] = (
498
+ convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit))
499
+ if (files is not None and files is not omit and isinstance(files, dict))
500
+ else None
501
+ )
502
+
503
+ if (request_files is None or len(request_files) == 0) and force_multipart:
504
+ request_files = FORCE_MULTIPART
505
+
458
506
  json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit)
459
507
 
460
508
  async with self.httpx_client.stream(
@@ -489,11 +537,7 @@ class AsyncHttpClient:
489
537
  json=json_body,
490
538
  data=data_body,
491
539
  content=content,
492
- files=(
493
- convert_file_dict_to_httpx_tuples(remove_omit_from_dict(remove_none_from_dict(files), omit))
494
- if files is not None
495
- else None
496
- ),
540
+ files=request_files,
497
541
  timeout=timeout,
498
542
  ) as stream:
499
543
  yield stream