label-studio-sdk 1.0.8__py3-none-any.whl → 1.0.10__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 label-studio-sdk might be problematic. Click here for more details.

Files changed (200) hide show
  1. label_studio_sdk/__init__.py +20 -7
  2. label_studio_sdk/_extensions/label_studio_tools/core/utils/io.py +16 -4
  3. label_studio_sdk/_extensions/pager_ext.py +8 -0
  4. label_studio_sdk/actions/client.py +91 -40
  5. label_studio_sdk/actions/types/actions_create_request_filters.py +14 -24
  6. label_studio_sdk/actions/types/actions_create_request_filters_items_item.py +16 -26
  7. label_studio_sdk/actions/types/actions_create_request_filters_items_item_value.py +3 -1
  8. label_studio_sdk/actions/types/actions_create_request_selected_items.py +1 -2
  9. label_studio_sdk/actions/types/actions_create_request_selected_items_excluded.py +15 -25
  10. label_studio_sdk/actions/types/actions_create_request_selected_items_included.py +15 -25
  11. label_studio_sdk/annotations/__init__.py +2 -2
  12. label_studio_sdk/annotations/client.py +278 -104
  13. label_studio_sdk/annotations/types/__init__.py +2 -1
  14. label_studio_sdk/annotations/types/annotations_create_bulk_request_selected_items.py +34 -0
  15. label_studio_sdk/annotations/types/annotations_create_bulk_response_item.py +11 -21
  16. label_studio_sdk/base_client.py +46 -27
  17. label_studio_sdk/client.py +1 -0
  18. label_studio_sdk/comments/client.py +190 -44
  19. label_studio_sdk/converter/converter.py +56 -13
  20. label_studio_sdk/converter/imports/yolo.py +1 -1
  21. label_studio_sdk/converter/utils.py +3 -2
  22. label_studio_sdk/core/__init__.py +21 -4
  23. label_studio_sdk/core/client_wrapper.py +9 -10
  24. label_studio_sdk/core/file.py +37 -8
  25. label_studio_sdk/core/http_client.py +52 -28
  26. label_studio_sdk/core/jsonable_encoder.py +33 -31
  27. label_studio_sdk/core/pagination.py +5 -4
  28. label_studio_sdk/core/pydantic_utilities.py +272 -4
  29. label_studio_sdk/core/query_encoder.py +38 -13
  30. label_studio_sdk/core/request_options.py +3 -0
  31. label_studio_sdk/core/serialization.py +272 -0
  32. label_studio_sdk/errors/bad_request_error.py +2 -3
  33. label_studio_sdk/export_storage/azure/client.py +228 -58
  34. label_studio_sdk/export_storage/azure/types/azure_create_response.py +19 -29
  35. label_studio_sdk/export_storage/azure/types/azure_update_response.py +19 -29
  36. label_studio_sdk/export_storage/client.py +48 -18
  37. label_studio_sdk/export_storage/gcs/client.py +228 -58
  38. label_studio_sdk/export_storage/gcs/types/gcs_create_response.py +19 -29
  39. label_studio_sdk/export_storage/gcs/types/gcs_update_response.py +19 -29
  40. label_studio_sdk/export_storage/local/client.py +222 -56
  41. label_studio_sdk/export_storage/local/types/local_create_response.py +17 -27
  42. label_studio_sdk/export_storage/local/types/local_update_response.py +17 -27
  43. label_studio_sdk/export_storage/redis/client.py +228 -58
  44. label_studio_sdk/export_storage/redis/types/redis_create_response.py +20 -30
  45. label_studio_sdk/export_storage/redis/types/redis_update_response.py +20 -30
  46. label_studio_sdk/export_storage/s3/client.py +228 -58
  47. label_studio_sdk/export_storage/s3/types/s3create_response.py +27 -35
  48. label_studio_sdk/export_storage/s3/types/s3update_response.py +27 -35
  49. label_studio_sdk/export_storage/s3s/client.py +187 -43
  50. label_studio_sdk/export_storage/types/export_storage_list_types_response_item.py +11 -21
  51. label_studio_sdk/files/client.py +172 -56
  52. label_studio_sdk/import_storage/azure/client.py +223 -53
  53. label_studio_sdk/import_storage/azure/types/azure_create_response.py +22 -32
  54. label_studio_sdk/import_storage/azure/types/azure_update_response.py +22 -32
  55. label_studio_sdk/import_storage/client.py +48 -18
  56. label_studio_sdk/import_storage/gcs/client.py +223 -53
  57. label_studio_sdk/import_storage/gcs/types/gcs_create_response.py +22 -32
  58. label_studio_sdk/import_storage/gcs/types/gcs_update_response.py +22 -32
  59. label_studio_sdk/import_storage/local/client.py +223 -53
  60. label_studio_sdk/import_storage/local/types/local_create_response.py +17 -27
  61. label_studio_sdk/import_storage/local/types/local_update_response.py +17 -27
  62. label_studio_sdk/import_storage/redis/client.py +223 -53
  63. label_studio_sdk/import_storage/redis/types/redis_create_response.py +20 -30
  64. label_studio_sdk/import_storage/redis/types/redis_update_response.py +20 -30
  65. label_studio_sdk/import_storage/s3/client.py +223 -53
  66. label_studio_sdk/import_storage/s3/types/s3create_response.py +31 -39
  67. label_studio_sdk/import_storage/s3/types/s3update_response.py +31 -39
  68. label_studio_sdk/import_storage/s3s/client.py +222 -52
  69. label_studio_sdk/import_storage/types/import_storage_list_types_response_item.py +11 -21
  70. label_studio_sdk/label_interface/control_tags.py +1 -1
  71. label_studio_sdk/ml/client.py +280 -78
  72. label_studio_sdk/ml/types/ml_create_response.py +21 -31
  73. label_studio_sdk/ml/types/ml_update_response.py +21 -31
  74. label_studio_sdk/model_providers/client.py +173 -56
  75. label_studio_sdk/predictions/client.py +247 -101
  76. label_studio_sdk/projects/__init__.py +3 -0
  77. label_studio_sdk/projects/client.py +309 -115
  78. label_studio_sdk/projects/client_ext.py +16 -0
  79. label_studio_sdk/projects/exports/__init__.py +3 -0
  80. label_studio_sdk/projects/exports/client.py +447 -296
  81. label_studio_sdk/projects/exports/client_ext.py +134 -0
  82. label_studio_sdk/projects/exports/types/__init__.py +6 -0
  83. label_studio_sdk/projects/exports/types/exports_convert_response.py +24 -0
  84. label_studio_sdk/projects/exports/types/exports_list_formats_response_item.py +44 -0
  85. label_studio_sdk/projects/types/projects_create_response.py +29 -34
  86. label_studio_sdk/projects/types/projects_import_tasks_response.py +19 -29
  87. label_studio_sdk/projects/types/projects_list_response.py +11 -21
  88. label_studio_sdk/projects/types/projects_update_response.py +24 -34
  89. label_studio_sdk/prompts/client.py +309 -92
  90. label_studio_sdk/prompts/indicators/client.py +67 -23
  91. label_studio_sdk/prompts/runs/client.py +95 -40
  92. label_studio_sdk/prompts/types/prompts_batch_failed_predictions_request_failed_predictions_item.py +14 -24
  93. label_studio_sdk/prompts/types/prompts_batch_failed_predictions_response.py +11 -21
  94. label_studio_sdk/prompts/types/prompts_batch_predictions_request_results_item.py +26 -29
  95. label_studio_sdk/prompts/types/prompts_batch_predictions_response.py +11 -21
  96. label_studio_sdk/prompts/versions/client.py +277 -88
  97. label_studio_sdk/tasks/client.py +263 -90
  98. label_studio_sdk/tasks/types/tasks_list_response.py +15 -25
  99. label_studio_sdk/types/__init__.py +10 -6
  100. label_studio_sdk/types/annotation.py +29 -38
  101. label_studio_sdk/types/annotation_filter_options.py +14 -24
  102. label_studio_sdk/types/annotations_dm_field.py +30 -39
  103. label_studio_sdk/types/azure_blob_export_storage.py +28 -37
  104. label_studio_sdk/types/azure_blob_import_storage.py +28 -37
  105. label_studio_sdk/types/base_task.py +30 -39
  106. label_studio_sdk/types/base_task_updated_by.py +3 -1
  107. label_studio_sdk/types/base_user.py +14 -21
  108. label_studio_sdk/types/comment.py +12 -21
  109. label_studio_sdk/types/comment_created_by.py +1 -1
  110. label_studio_sdk/types/converted_format.py +12 -22
  111. label_studio_sdk/types/data_manager_task_serializer.py +31 -40
  112. label_studio_sdk/types/data_manager_task_serializer_annotators_item.py +1 -1
  113. label_studio_sdk/types/data_manager_task_serializer_drafts_item.py +13 -22
  114. label_studio_sdk/types/data_manager_task_serializer_predictions_item.py +15 -24
  115. label_studio_sdk/types/export.py +17 -26
  116. label_studio_sdk/types/export_format.py +25 -0
  117. label_studio_sdk/types/export_snapshot.py +45 -0
  118. label_studio_sdk/types/export_snapshot_status.py +5 -0
  119. label_studio_sdk/types/file_upload.py +11 -21
  120. label_studio_sdk/types/filter.py +16 -26
  121. label_studio_sdk/types/filter_group.py +12 -22
  122. label_studio_sdk/types/gcs_export_storage.py +28 -37
  123. label_studio_sdk/types/gcs_import_storage.py +28 -37
  124. label_studio_sdk/types/inference_run.py +14 -23
  125. label_studio_sdk/types/inference_run_cost_estimate.py +17 -27
  126. label_studio_sdk/types/inference_run_created_by.py +1 -1
  127. label_studio_sdk/types/inference_run_organization.py +1 -1
  128. label_studio_sdk/types/key_indicator_value.py +12 -22
  129. label_studio_sdk/types/key_indicators.py +0 -1
  130. label_studio_sdk/types/key_indicators_item.py +15 -25
  131. label_studio_sdk/types/key_indicators_item_additional_kpis_item.py +13 -23
  132. label_studio_sdk/types/key_indicators_item_extra_kpis_item.py +13 -23
  133. label_studio_sdk/types/local_files_export_storage.py +25 -34
  134. label_studio_sdk/types/local_files_import_storage.py +24 -33
  135. label_studio_sdk/types/ml_backend.py +23 -32
  136. label_studio_sdk/types/model_provider_connection.py +22 -31
  137. label_studio_sdk/types/model_provider_connection_created_by.py +1 -1
  138. label_studio_sdk/types/model_provider_connection_organization.py +1 -1
  139. label_studio_sdk/types/model_provider_connection_provider.py +3 -1
  140. label_studio_sdk/types/prediction.py +21 -30
  141. label_studio_sdk/types/project.py +48 -55
  142. label_studio_sdk/types/project_import.py +21 -30
  143. label_studio_sdk/types/project_label_config.py +12 -22
  144. label_studio_sdk/types/prompt.py +24 -32
  145. label_studio_sdk/types/prompt_associated_projects_item.py +6 -0
  146. label_studio_sdk/types/prompt_associated_projects_item_id.py +20 -0
  147. label_studio_sdk/types/prompt_created_by.py +1 -1
  148. label_studio_sdk/types/prompt_organization.py +1 -1
  149. label_studio_sdk/types/prompt_version.py +13 -22
  150. label_studio_sdk/types/prompt_version_created_by.py +1 -1
  151. label_studio_sdk/types/prompt_version_organization.py +1 -1
  152. label_studio_sdk/types/prompt_version_provider.py +3 -1
  153. label_studio_sdk/types/redis_export_storage.py +29 -38
  154. label_studio_sdk/types/redis_import_storage.py +28 -37
  155. label_studio_sdk/types/refined_prompt_response.py +19 -29
  156. label_studio_sdk/types/s3export_storage.py +36 -43
  157. label_studio_sdk/types/s3import_storage.py +37 -44
  158. label_studio_sdk/types/s3s_export_storage.py +26 -33
  159. label_studio_sdk/types/s3s_import_storage.py +35 -42
  160. label_studio_sdk/types/serialization_option.py +12 -22
  161. label_studio_sdk/types/serialization_options.py +18 -28
  162. label_studio_sdk/types/task.py +44 -47
  163. label_studio_sdk/types/task_annotators_item.py +1 -1
  164. label_studio_sdk/types/task_comment_authors_item.py +1 -1
  165. label_studio_sdk/types/task_filter_options.py +15 -25
  166. label_studio_sdk/types/user_simple.py +11 -21
  167. label_studio_sdk/types/view.py +16 -26
  168. label_studio_sdk/types/webhook.py +19 -28
  169. label_studio_sdk/types/webhook_serializer_for_update.py +19 -28
  170. label_studio_sdk/types/workspace.py +22 -31
  171. label_studio_sdk/users/client.py +257 -63
  172. label_studio_sdk/users/types/users_get_token_response.py +12 -22
  173. label_studio_sdk/users/types/users_reset_token_response.py +12 -22
  174. label_studio_sdk/version.py +0 -1
  175. label_studio_sdk/versions/__init__.py +5 -0
  176. label_studio_sdk/versions/client.py +112 -0
  177. label_studio_sdk/versions/types/__init__.py +6 -0
  178. label_studio_sdk/versions/types/versions_get_response.py +73 -0
  179. label_studio_sdk/versions/types/versions_get_response_edition.py +5 -0
  180. label_studio_sdk/views/client.py +219 -52
  181. label_studio_sdk/views/types/views_create_request_data.py +13 -23
  182. label_studio_sdk/views/types/views_create_request_data_filters.py +14 -24
  183. label_studio_sdk/views/types/views_create_request_data_filters_items_item.py +16 -26
  184. label_studio_sdk/views/types/views_create_request_data_filters_items_item_value.py +3 -1
  185. label_studio_sdk/views/types/views_update_request_data.py +13 -23
  186. label_studio_sdk/views/types/views_update_request_data_filters.py +14 -24
  187. label_studio_sdk/views/types/views_update_request_data_filters_items_item.py +16 -26
  188. label_studio_sdk/views/types/views_update_request_data_filters_items_item_value.py +3 -1
  189. label_studio_sdk/webhooks/client.py +191 -61
  190. label_studio_sdk/workspaces/client.py +164 -41
  191. label_studio_sdk/workspaces/members/client.py +109 -31
  192. label_studio_sdk/workspaces/members/types/members_create_response.py +12 -22
  193. label_studio_sdk/workspaces/members/types/members_list_response_item.py +12 -22
  194. {label_studio_sdk-1.0.8.dist-info → label_studio_sdk-1.0.10.dist-info}/METADATA +7 -5
  195. {label_studio_sdk-1.0.8.dist-info → label_studio_sdk-1.0.10.dist-info}/RECORD +197 -184
  196. {label_studio_sdk-1.0.8.dist-info → label_studio_sdk-1.0.10.dist-info}/WHEEL +1 -1
  197. label_studio_sdk/types/export_convert.py +0 -32
  198. label_studio_sdk/types/export_create.py +0 -54
  199. label_studio_sdk/types/export_create_status.py +0 -5
  200. {label_studio_sdk-1.0.8.dist-info → label_studio_sdk-1.0.10.dist-info}/LICENSE +0 -0
@@ -21,6 +21,7 @@ from lxml import etree
21
21
  from nltk.tokenize.treebank import TreebankWordTokenizer
22
22
 
23
23
  from label_studio_sdk._extensions.label_studio_tools.core.utils.params import get_env
24
+ from label_studio_sdk._extensions.label_studio_tools.core.utils.io import safe_build_path
24
25
 
25
26
  logger = logging.getLogger(__name__)
26
27
 
@@ -148,7 +149,7 @@ def download(
148
149
  if is_uploaded_file:
149
150
  upload_dir = _get_upload_dir(project_dir, upload_dir)
150
151
  filename = urllib.parse.unquote(url.replace("/data/upload/", ""))
151
- filepath = os.path.join(upload_dir, filename)
152
+ filepath = safe_build_path(upload_dir, filename)
152
153
  logger.debug(
153
154
  f"Copy {filepath} to {output_dir}".format(
154
155
  filepath=filepath, output_dir=output_dir
@@ -165,7 +166,7 @@ def download(
165
166
  if is_local_file:
166
167
  filename, dir_path = url.split("/data/", 1)[-1].split("?d=")
167
168
  dir_path = str(urllib.parse.unquote(dir_path))
168
- filepath = os.path.join(LOCAL_FILES_DOCUMENT_ROOT, dir_path)
169
+ filepath = safe_build_path(LOCAL_FILES_DOCUMENT_ROOT, dir_path)
169
170
  if not os.path.exists(filepath):
170
171
  raise FileNotFoundError(filepath)
171
172
  if download_resources:
@@ -3,14 +3,23 @@
3
3
  from .api_error import ApiError
4
4
  from .client_wrapper import AsyncClientWrapper, BaseClientWrapper, SyncClientWrapper
5
5
  from .datetime_utils import serialize_datetime
6
- from .file import File, convert_file_dict_to_httpx_tuples
6
+ from .file import File, convert_file_dict_to_httpx_tuples, with_content_type
7
7
  from .http_client import AsyncHttpClient, HttpClient
8
8
  from .jsonable_encoder import jsonable_encoder
9
9
  from .pagination import AsyncPager, SyncPager
10
- from .pydantic_utilities import deep_union_pydantic_dicts, pydantic_v1
10
+ from .pydantic_utilities import (
11
+ IS_PYDANTIC_V2,
12
+ UniversalBaseModel,
13
+ UniversalRootModel,
14
+ parse_obj_as,
15
+ universal_field_validator,
16
+ universal_root_validator,
17
+ update_forward_refs,
18
+ )
11
19
  from .query_encoder import encode_query
12
20
  from .remove_none_from_dict import remove_none_from_dict
13
21
  from .request_options import RequestOptions
22
+ from .serialization import FieldMetadata, convert_and_respect_annotation_metadata
14
23
 
15
24
  __all__ = [
16
25
  "ApiError",
@@ -18,16 +27,24 @@ __all__ = [
18
27
  "AsyncHttpClient",
19
28
  "AsyncPager",
20
29
  "BaseClientWrapper",
30
+ "FieldMetadata",
21
31
  "File",
22
32
  "HttpClient",
33
+ "IS_PYDANTIC_V2",
23
34
  "RequestOptions",
24
35
  "SyncClientWrapper",
25
36
  "SyncPager",
37
+ "UniversalBaseModel",
38
+ "UniversalRootModel",
39
+ "convert_and_respect_annotation_metadata",
26
40
  "convert_file_dict_to_httpx_tuples",
27
- "deep_union_pydantic_dicts",
28
41
  "encode_query",
29
42
  "jsonable_encoder",
30
- "pydantic_v1",
43
+ "parse_obj_as",
31
44
  "remove_none_from_dict",
32
45
  "serialize_datetime",
46
+ "universal_field_validator",
47
+ "universal_root_validator",
48
+ "update_forward_refs",
49
+ "with_content_type",
33
50
  ]
@@ -1,10 +1,9 @@
1
1
  # This file was auto-generated by Fern from our API Definition.
2
2
 
3
3
  import typing
4
-
5
4
  import httpx
6
-
7
- from .http_client import AsyncHttpClient, HttpClient
5
+ from .http_client import HttpClient
6
+ from .http_client import AsyncHttpClient
8
7
 
9
8
 
10
9
  class BaseClientWrapper:
@@ -17,7 +16,7 @@ class BaseClientWrapper:
17
16
  headers: typing.Dict[str, str] = {
18
17
  "X-Fern-Language": "Python",
19
18
  "X-Fern-SDK-Name": "label-studio-sdk",
20
- "X-Fern-SDK-Version": "1.0.8",
19
+ "X-Fern-SDK-Version": "1.0.9",
21
20
  }
22
21
  headers["Authorization"] = f"Token {self.api_key}"
23
22
  return headers
@@ -36,9 +35,9 @@ class SyncClientWrapper(BaseClientWrapper):
36
35
  super().__init__(api_key=api_key, base_url=base_url, timeout=timeout)
37
36
  self.httpx_client = HttpClient(
38
37
  httpx_client=httpx_client,
39
- base_headers=self.get_headers(),
40
- base_timeout=self.get_timeout(),
41
- base_url=self.get_base_url(),
38
+ base_headers=self.get_headers,
39
+ base_timeout=self.get_timeout,
40
+ base_url=self.get_base_url,
42
41
  )
43
42
 
44
43
 
@@ -49,7 +48,7 @@ class AsyncClientWrapper(BaseClientWrapper):
49
48
  super().__init__(api_key=api_key, base_url=base_url, timeout=timeout)
50
49
  self.httpx_client = AsyncHttpClient(
51
50
  httpx_client=httpx_client,
52
- base_headers=self.get_headers(),
53
- base_timeout=self.get_timeout(),
54
- base_url=self.get_base_url(),
51
+ base_headers=self.get_headers,
52
+ base_timeout=self.get_timeout,
53
+ base_url=self.get_base_url,
55
54
  )
@@ -1,25 +1,30 @@
1
1
  # This file was auto-generated by Fern from our API Definition.
2
2
 
3
- import typing
3
+ from typing import IO, Dict, List, Mapping, Optional, Tuple, Union, cast
4
4
 
5
5
  # File typing inspired by the flexibility of types within the httpx library
6
6
  # https://github.com/encode/httpx/blob/master/httpx/_types.py
7
- FileContent = typing.Union[typing.IO[bytes], bytes, str]
8
- File = typing.Union[
7
+ FileContent = Union[IO[bytes], bytes, str]
8
+ File = Union[
9
9
  # file (or bytes)
10
10
  FileContent,
11
11
  # (filename, file (or bytes))
12
- typing.Tuple[typing.Optional[str], FileContent],
12
+ Tuple[Optional[str], FileContent],
13
13
  # (filename, file (or bytes), content_type)
14
- typing.Tuple[typing.Optional[str], FileContent, typing.Optional[str]],
14
+ Tuple[Optional[str], FileContent, Optional[str]],
15
15
  # (filename, file (or bytes), content_type, headers)
16
- typing.Tuple[typing.Optional[str], FileContent, typing.Optional[str], typing.Mapping[str, str]],
16
+ Tuple[
17
+ Optional[str],
18
+ FileContent,
19
+ Optional[str],
20
+ Mapping[str, str],
21
+ ],
17
22
  ]
18
23
 
19
24
 
20
25
  def convert_file_dict_to_httpx_tuples(
21
- d: typing.Dict[str, typing.Union[File, typing.List[File]]]
22
- ) -> typing.List[typing.Tuple[str, File]]:
26
+ d: Dict[str, Union[File, List[File]]],
27
+ ) -> List[Tuple[str, File]]:
23
28
  """
24
29
  The format we use is a list of tuples, where the first element is the
25
30
  name of the file and the second is the file object. Typically HTTPX wants
@@ -36,3 +41,27 @@ def convert_file_dict_to_httpx_tuples(
36
41
  else:
37
42
  httpx_tuples.append((key, file_like))
38
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(Tuple[Optional[str], FileContent, Optional[str]], file) # type: ignore
57
+ out_content_type = file_content_type or default_content_type
58
+ return (filename, content, out_content_type)
59
+ elif len(file) == 4:
60
+ filename, content, file_content_type, headers = cast( # type: ignore
61
+ Tuple[Optional[str], FileContent, Optional[str], Mapping[str, str]], file
62
+ )
63
+ out_content_type = file_content_type or default_content_type
64
+ return (filename, content, out_content_type, headers)
65
+ else:
66
+ raise ValueError(f"Unexpected tuple length: {len(file)}")
67
+ return (None, file, default_content_type)
@@ -90,7 +90,8 @@ def _should_retry(response: httpx.Response) -> bool:
90
90
 
91
91
 
92
92
  def remove_omit_from_dict(
93
- original: typing.Dict[str, typing.Optional[typing.Any]], omit: typing.Optional[typing.Any]
93
+ original: typing.Dict[str, typing.Optional[typing.Any]],
94
+ omit: typing.Optional[typing.Any],
94
95
  ) -> typing.Dict[str, typing.Any]:
95
96
  if omit is None:
96
97
  return original
@@ -108,7 +109,7 @@ def maybe_filter_request_body(
108
109
  ) -> typing.Optional[typing.Any]:
109
110
  if data is None:
110
111
  return (
111
- jsonable_encoder(request_options.get("additional_body_parameters", {}))
112
+ jsonable_encoder(request_options.get("additional_body_parameters", {})) or {}
112
113
  if request_options is not None
113
114
  else None
114
115
  )
@@ -118,7 +119,7 @@ def maybe_filter_request_body(
118
119
  data_content = {
119
120
  **(jsonable_encoder(remove_omit_from_dict(data, omit))), # type: ignore
120
121
  **(
121
- jsonable_encoder(request_options.get("additional_body_parameters", {}))
122
+ jsonable_encoder(request_options.get("additional_body_parameters", {})) or {}
122
123
  if request_options is not None
123
124
  else {}
124
125
  ),
@@ -142,7 +143,8 @@ def get_request_body(
142
143
  # If both data and json are None, we send json data in the event extra properties are specified
143
144
  json_body = maybe_filter_request_body(json, request_options, omit)
144
145
 
145
- return json_body, data_body
146
+ # If you have an empty JSON body, you should just send None
147
+ return (json_body if json_body != {} else None), data_body if data_body != {} else None
146
148
 
147
149
 
148
150
  class HttpClient:
@@ -150,9 +152,9 @@ class HttpClient:
150
152
  self,
151
153
  *,
152
154
  httpx_client: httpx.Client,
153
- base_timeout: typing.Optional[float],
154
- base_headers: typing.Dict[str, str],
155
- base_url: typing.Optional[str] = None,
155
+ base_timeout: typing.Callable[[], typing.Optional[float]],
156
+ base_headers: typing.Callable[[], typing.Dict[str, str]],
157
+ base_url: typing.Optional[typing.Callable[[], str]] = None,
156
158
  ):
157
159
  self.base_url = base_url
158
160
  self.base_timeout = base_timeout
@@ -160,7 +162,10 @@ class HttpClient:
160
162
  self.httpx_client = httpx_client
161
163
 
162
164
  def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str:
163
- base_url = self.base_url if maybe_base_url is None else maybe_base_url
165
+ base_url = maybe_base_url
166
+ if self.base_url is not None and base_url is None:
167
+ base_url = self.base_url()
168
+
164
169
  if base_url is None:
165
170
  raise ValueError("A base_url is required to make this request, please provide one and try again.")
166
171
  return base_url
@@ -185,7 +190,7 @@ class HttpClient:
185
190
  timeout = (
186
191
  request_options.get("timeout_in_seconds")
187
192
  if request_options is not None and request_options.get("timeout_in_seconds") is not None
188
- else self.base_timeout
193
+ else self.base_timeout()
189
194
  )
190
195
 
191
196
  json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit)
@@ -196,9 +201,9 @@ class HttpClient:
196
201
  headers=jsonable_encoder(
197
202
  remove_none_from_dict(
198
203
  {
199
- **self.base_headers,
204
+ **self.base_headers(),
200
205
  **(headers if headers is not None else {}),
201
- **(request_options.get("additional_headers", {}) if request_options is not None else {}),
206
+ **(request_options.get("additional_headers", {}) or {} if request_options is not None else {}),
202
207
  }
203
208
  )
204
209
  ),
@@ -209,7 +214,7 @@ class HttpClient:
209
214
  {
210
215
  **(params if params is not None else {}),
211
216
  **(
212
- request_options.get("additional_query_parameters", {})
217
+ request_options.get("additional_query_parameters", {}) or {}
213
218
  if request_options is not None
214
219
  else {}
215
220
  ),
@@ -222,7 +227,11 @@ class HttpClient:
222
227
  json=json_body,
223
228
  data=data_body,
224
229
  content=content,
225
- files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None,
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
+ ),
226
235
  timeout=timeout,
227
236
  )
228
237
 
@@ -267,7 +276,7 @@ class HttpClient:
267
276
  timeout = (
268
277
  request_options.get("timeout_in_seconds")
269
278
  if request_options is not None and request_options.get("timeout_in_seconds") is not None
270
- else self.base_timeout
279
+ else self.base_timeout()
271
280
  )
272
281
 
273
282
  json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit)
@@ -278,7 +287,7 @@ class HttpClient:
278
287
  headers=jsonable_encoder(
279
288
  remove_none_from_dict(
280
289
  {
281
- **self.base_headers,
290
+ **self.base_headers(),
282
291
  **(headers if headers is not None else {}),
283
292
  **(request_options.get("additional_headers", {}) if request_options is not None else {}),
284
293
  }
@@ -304,7 +313,11 @@ class HttpClient:
304
313
  json=json_body,
305
314
  data=data_body,
306
315
  content=content,
307
- files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None,
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
+ ),
308
321
  timeout=timeout,
309
322
  ) as stream:
310
323
  yield stream
@@ -315,9 +328,9 @@ class AsyncHttpClient:
315
328
  self,
316
329
  *,
317
330
  httpx_client: httpx.AsyncClient,
318
- base_timeout: typing.Optional[float],
319
- base_headers: typing.Dict[str, str],
320
- base_url: typing.Optional[str] = None,
331
+ base_timeout: typing.Callable[[], typing.Optional[float]],
332
+ base_headers: typing.Callable[[], typing.Dict[str, str]],
333
+ base_url: typing.Optional[typing.Callable[[], str]] = None,
321
334
  ):
322
335
  self.base_url = base_url
323
336
  self.base_timeout = base_timeout
@@ -325,7 +338,10 @@ class AsyncHttpClient:
325
338
  self.httpx_client = httpx_client
326
339
 
327
340
  def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str:
328
- base_url = self.base_url if maybe_base_url is None else maybe_base_url
341
+ base_url = maybe_base_url
342
+ if self.base_url is not None and base_url is None:
343
+ base_url = self.base_url()
344
+
329
345
  if base_url is None:
330
346
  raise ValueError("A base_url is required to make this request, please provide one and try again.")
331
347
  return base_url
@@ -350,7 +366,7 @@ class AsyncHttpClient:
350
366
  timeout = (
351
367
  request_options.get("timeout_in_seconds")
352
368
  if request_options is not None and request_options.get("timeout_in_seconds") is not None
353
- else self.base_timeout
369
+ else self.base_timeout()
354
370
  )
355
371
 
356
372
  json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit)
@@ -362,9 +378,9 @@ class AsyncHttpClient:
362
378
  headers=jsonable_encoder(
363
379
  remove_none_from_dict(
364
380
  {
365
- **self.base_headers,
381
+ **self.base_headers(),
366
382
  **(headers if headers is not None else {}),
367
- **(request_options.get("additional_headers", {}) if request_options is not None else {}),
383
+ **(request_options.get("additional_headers", {}) or {} if request_options is not None else {}),
368
384
  }
369
385
  )
370
386
  ),
@@ -375,7 +391,7 @@ class AsyncHttpClient:
375
391
  {
376
392
  **(params if params is not None else {}),
377
393
  **(
378
- request_options.get("additional_query_parameters", {})
394
+ request_options.get("additional_query_parameters", {}) or {}
379
395
  if request_options is not None
380
396
  else {}
381
397
  ),
@@ -388,7 +404,11 @@ class AsyncHttpClient:
388
404
  json=json_body,
389
405
  data=data_body,
390
406
  content=content,
391
- files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None,
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
+ ),
392
412
  timeout=timeout,
393
413
  )
394
414
 
@@ -432,7 +452,7 @@ class AsyncHttpClient:
432
452
  timeout = (
433
453
  request_options.get("timeout_in_seconds")
434
454
  if request_options is not None and request_options.get("timeout_in_seconds") is not None
435
- else self.base_timeout
455
+ else self.base_timeout()
436
456
  )
437
457
 
438
458
  json_body, data_body = get_request_body(json=json, data=data, request_options=request_options, omit=omit)
@@ -443,7 +463,7 @@ class AsyncHttpClient:
443
463
  headers=jsonable_encoder(
444
464
  remove_none_from_dict(
445
465
  {
446
- **self.base_headers,
466
+ **self.base_headers(),
447
467
  **(headers if headers is not None else {}),
448
468
  **(request_options.get("additional_headers", {}) if request_options is not None else {}),
449
469
  }
@@ -469,7 +489,11 @@ class AsyncHttpClient:
469
489
  json=json_body,
470
490
  data=data_body,
471
491
  content=content,
472
- files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None,
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
+ ),
473
497
  timeout=timeout,
474
498
  ) as stream:
475
499
  yield stream
@@ -8,33 +8,27 @@ Taken from FastAPI, and made a bit simpler
8
8
  https://github.com/tiangolo/fastapi/blob/master/fastapi/encoders.py
9
9
  """
10
10
 
11
+ import base64
11
12
  import dataclasses
12
13
  import datetime as dt
13
- from collections import defaultdict
14
14
  from enum import Enum
15
15
  from pathlib import PurePath
16
16
  from types import GeneratorType
17
- from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
17
+ from typing import Any, Callable, Dict, List, Optional, Set, Union
18
+
19
+ import pydantic
18
20
 
19
21
  from .datetime_utils import serialize_datetime
20
- from .pydantic_utilities import pydantic_v1
22
+ from .pydantic_utilities import (
23
+ IS_PYDANTIC_V2,
24
+ encode_by_type,
25
+ to_jsonable_with_fallback,
26
+ )
21
27
 
22
28
  SetIntStr = Set[Union[int, str]]
23
29
  DictIntStrAny = Dict[Union[int, str], Any]
24
30
 
25
31
 
26
- def generate_encoders_by_class_tuples(
27
- type_encoder_map: Dict[Any, Callable[[Any], Any]]
28
- ) -> Dict[Callable[[Any], Any], Tuple[Any, ...]]:
29
- encoders_by_class_tuples: Dict[Callable[[Any], Any], Tuple[Any, ...]] = defaultdict(tuple)
30
- for type_, encoder in type_encoder_map.items():
31
- encoders_by_class_tuples[encoder] += (type_,)
32
- return encoders_by_class_tuples
33
-
34
-
35
- encoders_by_class_tuples = generate_encoders_by_class_tuples(pydantic_v1.json.ENCODERS_BY_TYPE)
36
-
37
-
38
32
  def jsonable_encoder(obj: Any, custom_encoder: Optional[Dict[Any, Callable[[Any], Any]]] = None) -> Any:
39
33
  custom_encoder = custom_encoder or {}
40
34
  if custom_encoder:
@@ -44,17 +38,24 @@ def jsonable_encoder(obj: Any, custom_encoder: Optional[Dict[Any, Callable[[Any]
44
38
  for encoder_type, encoder_instance in custom_encoder.items():
45
39
  if isinstance(obj, encoder_type):
46
40
  return encoder_instance(obj)
47
- if isinstance(obj, pydantic_v1.BaseModel):
48
- encoder = getattr(obj.__config__, "json_encoders", {})
41
+ if isinstance(obj, pydantic.BaseModel):
42
+ if IS_PYDANTIC_V2:
43
+ encoder = getattr(obj.model_config, "json_encoders", {}) # type: ignore # Pydantic v2
44
+ else:
45
+ encoder = getattr(obj.__config__, "json_encoders", {}) # type: ignore # Pydantic v1
49
46
  if custom_encoder:
50
47
  encoder.update(custom_encoder)
51
48
  obj_dict = obj.dict(by_alias=True)
52
49
  if "__root__" in obj_dict:
53
50
  obj_dict = obj_dict["__root__"]
51
+ if "root" in obj_dict:
52
+ obj_dict = obj_dict["root"]
54
53
  return jsonable_encoder(obj_dict, custom_encoder=encoder)
55
54
  if dataclasses.is_dataclass(obj):
56
- obj_dict = dataclasses.asdict(obj)
55
+ obj_dict = dataclasses.asdict(obj) # type: ignore
57
56
  return jsonable_encoder(obj_dict, custom_encoder=custom_encoder)
57
+ if isinstance(obj, bytes):
58
+ return base64.b64encode(obj).decode("utf-8")
58
59
  if isinstance(obj, Enum):
59
60
  return obj.value
60
61
  if isinstance(obj, PurePath):
@@ -80,20 +81,21 @@ def jsonable_encoder(obj: Any, custom_encoder: Optional[Dict[Any, Callable[[Any]
80
81
  encoded_list.append(jsonable_encoder(item, custom_encoder=custom_encoder))
81
82
  return encoded_list
82
83
 
83
- if type(obj) in pydantic_v1.json.ENCODERS_BY_TYPE:
84
- return pydantic_v1.json.ENCODERS_BY_TYPE[type(obj)](obj)
85
- for encoder, classes_tuple in encoders_by_class_tuples.items():
86
- if isinstance(obj, classes_tuple):
87
- return encoder(obj)
84
+ def fallback_serializer(o: Any) -> Any:
85
+ attempt_encode = encode_by_type(o)
86
+ if attempt_encode is not None:
87
+ return attempt_encode
88
88
 
89
- try:
90
- data = dict(obj)
91
- except Exception as e:
92
- errors: List[Exception] = []
93
- errors.append(e)
94
89
  try:
95
- data = vars(obj)
90
+ data = dict(o)
96
91
  except Exception as e:
92
+ errors: List[Exception] = []
97
93
  errors.append(e)
98
- raise ValueError(errors) from e
99
- return jsonable_encoder(data, custom_encoder=custom_encoder)
94
+ try:
95
+ data = vars(o)
96
+ except Exception as e:
97
+ errors.append(e)
98
+ raise ValueError(errors) from e
99
+ return jsonable_encoder(data, custom_encoder=custom_encoder)
100
+
101
+ return to_jsonable_with_fallback(obj, fallback_serializer)
@@ -4,11 +4,12 @@ import typing
4
4
 
5
5
  from typing_extensions import Self
6
6
 
7
- from .pydantic_utilities import pydantic_v1
7
+ import pydantic
8
8
 
9
9
  # Generic to represent the underlying type of the results within a page
10
10
  T = typing.TypeVar("T")
11
11
 
12
+
12
13
  # SDKs implement a Page ABC per-pagination request, the endpoint then retuns a pager that wraps this type
13
14
  # for example, an endpoint will return SyncPager[UserPage] where UserPage implements the Page ABC. ex:
14
15
  #
@@ -18,16 +19,16 @@ T = typing.TypeVar("T")
18
19
  # # This should be the outer function that returns the SyncPager again
19
20
  # get_next=lambda: list(..., cursor: response.cursor) (or list(..., offset: offset + 1))
20
21
  # )
21
- class BasePage(pydantic_v1.BaseModel, typing.Generic[T]):
22
+ class BasePage(pydantic.BaseModel, typing.Generic[T]):
22
23
  has_next: bool
23
24
  items: typing.Optional[typing.List[T]]
24
25
 
25
26
 
26
- class SyncPage(BasePage, typing.Generic[T]):
27
+ class SyncPage(BasePage[T], typing.Generic[T]):
27
28
  get_next: typing.Optional[typing.Callable[[], typing.Optional[Self]]]
28
29
 
29
30
 
30
- class AsyncPage(BasePage, typing.Generic[T]):
31
+ class AsyncPage(BasePage[T], typing.Generic[T]):
31
32
  get_next: typing.Optional[typing.Callable[[], typing.Awaitable[typing.Optional[Self]]]]
32
33
 
33
34