label-studio-sdk 0.0.32__py3-none-any.whl → 1.0.0__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 (245) hide show
  1. label_studio_sdk/__init__.py +206 -6
  2. label_studio_sdk/_extensions/label_studio_tools/__init__.py +0 -0
  3. label_studio_sdk/_extensions/label_studio_tools/core/__init__.py +0 -0
  4. label_studio_sdk/_extensions/label_studio_tools/core/label_config.py +163 -0
  5. label_studio_sdk/_extensions/label_studio_tools/core/utils/__init__.py +0 -0
  6. label_studio_sdk/_extensions/label_studio_tools/core/utils/exceptions.py +2 -0
  7. label_studio_sdk/_extensions/label_studio_tools/core/utils/io.py +228 -0
  8. label_studio_sdk/_extensions/label_studio_tools/core/utils/params.py +45 -0
  9. label_studio_sdk/_extensions/label_studio_tools/etl/__init__.py +1 -0
  10. label_studio_sdk/_extensions/label_studio_tools/etl/beam.py +34 -0
  11. label_studio_sdk/_extensions/label_studio_tools/etl/example.py +17 -0
  12. label_studio_sdk/_extensions/label_studio_tools/etl/registry.py +67 -0
  13. label_studio_sdk/_extensions/label_studio_tools/postprocessing/__init__.py +0 -0
  14. label_studio_sdk/_extensions/label_studio_tools/postprocessing/video.py +97 -0
  15. label_studio_sdk/_legacy/__init__.py +11 -0
  16. label_studio_sdk/_legacy/client.py +471 -0
  17. label_studio_sdk/_legacy/exceptions.py +10 -0
  18. label_studio_sdk/_legacy/label_interface/__init__.py +1 -0
  19. label_studio_sdk/_legacy/label_interface/base.py +77 -0
  20. label_studio_sdk/_legacy/label_interface/control_tags.py +756 -0
  21. label_studio_sdk/_legacy/label_interface/data_examples.json +96 -0
  22. label_studio_sdk/_legacy/label_interface/interface.py +925 -0
  23. label_studio_sdk/_legacy/label_interface/label_tags.py +72 -0
  24. label_studio_sdk/_legacy/label_interface/object_tags.py +292 -0
  25. label_studio_sdk/_legacy/label_interface/region.py +43 -0
  26. label_studio_sdk/_legacy/objects.py +35 -0
  27. label_studio_sdk/{project.py → _legacy/project.py} +711 -258
  28. label_studio_sdk/_legacy/schema/label_config_schema.json +226 -0
  29. label_studio_sdk/{users.py → _legacy/users.py} +15 -13
  30. label_studio_sdk/{utils.py → _legacy/utils.py} +31 -30
  31. label_studio_sdk/{workspaces.py → _legacy/workspaces.py} +13 -11
  32. label_studio_sdk/actions/__init__.py +2 -0
  33. label_studio_sdk/actions/client.py +150 -0
  34. label_studio_sdk/annotations/__init__.py +2 -0
  35. label_studio_sdk/annotations/client.py +750 -0
  36. label_studio_sdk/client.py +164 -436
  37. label_studio_sdk/converter/__init__.py +7 -0
  38. label_studio_sdk/converter/audio.py +56 -0
  39. label_studio_sdk/converter/brush.py +452 -0
  40. label_studio_sdk/converter/converter.py +1175 -0
  41. label_studio_sdk/converter/exports/__init__.py +0 -0
  42. label_studio_sdk/converter/exports/csv.py +82 -0
  43. label_studio_sdk/converter/exports/csv2.py +103 -0
  44. label_studio_sdk/converter/funsd.py +85 -0
  45. label_studio_sdk/converter/imports/__init__.py +0 -0
  46. label_studio_sdk/converter/imports/coco.py +314 -0
  47. label_studio_sdk/converter/imports/colors.py +198 -0
  48. label_studio_sdk/converter/imports/label_config.py +45 -0
  49. label_studio_sdk/converter/imports/pathtrack.py +269 -0
  50. label_studio_sdk/converter/imports/yolo.py +236 -0
  51. label_studio_sdk/converter/main.py +202 -0
  52. label_studio_sdk/converter/utils.py +473 -0
  53. label_studio_sdk/core/__init__.py +33 -0
  54. label_studio_sdk/core/api_error.py +15 -0
  55. label_studio_sdk/core/client_wrapper.py +55 -0
  56. label_studio_sdk/core/datetime_utils.py +28 -0
  57. label_studio_sdk/core/file.py +38 -0
  58. label_studio_sdk/core/http_client.py +443 -0
  59. label_studio_sdk/core/jsonable_encoder.py +99 -0
  60. label_studio_sdk/core/pagination.py +87 -0
  61. label_studio_sdk/core/pydantic_utilities.py +28 -0
  62. label_studio_sdk/core/query_encoder.py +33 -0
  63. label_studio_sdk/core/remove_none_from_dict.py +11 -0
  64. label_studio_sdk/core/request_options.py +32 -0
  65. label_studio_sdk/data_manager.py +32 -23
  66. label_studio_sdk/environment.py +7 -0
  67. label_studio_sdk/errors/__init__.py +6 -0
  68. label_studio_sdk/errors/bad_request_error.py +8 -0
  69. label_studio_sdk/errors/internal_server_error.py +8 -0
  70. label_studio_sdk/export_storage/__init__.py +28 -0
  71. label_studio_sdk/export_storage/azure/__init__.py +5 -0
  72. label_studio_sdk/export_storage/azure/client.py +722 -0
  73. label_studio_sdk/export_storage/azure/types/__init__.py +6 -0
  74. label_studio_sdk/export_storage/azure/types/azure_create_response.py +52 -0
  75. label_studio_sdk/export_storage/azure/types/azure_update_response.py +52 -0
  76. label_studio_sdk/export_storage/client.py +107 -0
  77. label_studio_sdk/export_storage/gcs/__init__.py +5 -0
  78. label_studio_sdk/export_storage/gcs/client.py +722 -0
  79. label_studio_sdk/export_storage/gcs/types/__init__.py +6 -0
  80. label_studio_sdk/export_storage/gcs/types/gcs_create_response.py +52 -0
  81. label_studio_sdk/export_storage/gcs/types/gcs_update_response.py +52 -0
  82. label_studio_sdk/export_storage/local/__init__.py +5 -0
  83. label_studio_sdk/export_storage/local/client.py +688 -0
  84. label_studio_sdk/export_storage/local/types/__init__.py +6 -0
  85. label_studio_sdk/export_storage/local/types/local_create_response.py +47 -0
  86. label_studio_sdk/export_storage/local/types/local_update_response.py +47 -0
  87. label_studio_sdk/export_storage/redis/__init__.py +5 -0
  88. label_studio_sdk/export_storage/redis/client.py +714 -0
  89. label_studio_sdk/export_storage/redis/types/__init__.py +6 -0
  90. label_studio_sdk/export_storage/redis/types/redis_create_response.py +57 -0
  91. label_studio_sdk/export_storage/redis/types/redis_update_response.py +57 -0
  92. label_studio_sdk/export_storage/s3/__init__.py +5 -0
  93. label_studio_sdk/export_storage/s3/client.py +820 -0
  94. label_studio_sdk/export_storage/s3/types/__init__.py +6 -0
  95. label_studio_sdk/export_storage/s3/types/s3create_response.py +74 -0
  96. label_studio_sdk/export_storage/s3/types/s3update_response.py +74 -0
  97. label_studio_sdk/export_storage/types/__init__.py +5 -0
  98. label_studio_sdk/export_storage/types/export_storage_list_types_response_item.py +30 -0
  99. label_studio_sdk/files/__init__.py +2 -0
  100. label_studio_sdk/files/client.py +556 -0
  101. label_studio_sdk/import_storage/__init__.py +28 -0
  102. label_studio_sdk/import_storage/azure/__init__.py +5 -0
  103. label_studio_sdk/import_storage/azure/client.py +812 -0
  104. label_studio_sdk/import_storage/azure/types/__init__.py +6 -0
  105. label_studio_sdk/import_storage/azure/types/azure_create_response.py +72 -0
  106. label_studio_sdk/import_storage/azure/types/azure_update_response.py +72 -0
  107. label_studio_sdk/import_storage/client.py +107 -0
  108. label_studio_sdk/import_storage/gcs/__init__.py +5 -0
  109. label_studio_sdk/import_storage/gcs/client.py +812 -0
  110. label_studio_sdk/import_storage/gcs/types/__init__.py +6 -0
  111. label_studio_sdk/import_storage/gcs/types/gcs_create_response.py +72 -0
  112. label_studio_sdk/import_storage/gcs/types/gcs_update_response.py +72 -0
  113. label_studio_sdk/import_storage/local/__init__.py +5 -0
  114. label_studio_sdk/import_storage/local/client.py +690 -0
  115. label_studio_sdk/import_storage/local/types/__init__.py +6 -0
  116. label_studio_sdk/import_storage/local/types/local_create_response.py +47 -0
  117. label_studio_sdk/import_storage/local/types/local_update_response.py +47 -0
  118. label_studio_sdk/import_storage/redis/__init__.py +5 -0
  119. label_studio_sdk/import_storage/redis/client.py +768 -0
  120. label_studio_sdk/import_storage/redis/types/__init__.py +6 -0
  121. label_studio_sdk/import_storage/redis/types/redis_create_response.py +62 -0
  122. label_studio_sdk/import_storage/redis/types/redis_update_response.py +62 -0
  123. label_studio_sdk/import_storage/s3/__init__.py +5 -0
  124. label_studio_sdk/import_storage/s3/client.py +912 -0
  125. label_studio_sdk/import_storage/s3/types/__init__.py +6 -0
  126. label_studio_sdk/import_storage/s3/types/s3create_response.py +99 -0
  127. label_studio_sdk/import_storage/s3/types/s3update_response.py +99 -0
  128. label_studio_sdk/import_storage/types/__init__.py +5 -0
  129. label_studio_sdk/import_storage/types/import_storage_list_types_response_item.py +30 -0
  130. label_studio_sdk/ml/__init__.py +19 -0
  131. label_studio_sdk/ml/client.py +981 -0
  132. label_studio_sdk/ml/types/__init__.py +17 -0
  133. label_studio_sdk/ml/types/ml_create_request_auth_method.py +5 -0
  134. label_studio_sdk/ml/types/ml_create_response.py +78 -0
  135. label_studio_sdk/ml/types/ml_create_response_auth_method.py +5 -0
  136. label_studio_sdk/ml/types/ml_update_request_auth_method.py +5 -0
  137. label_studio_sdk/ml/types/ml_update_response.py +78 -0
  138. label_studio_sdk/ml/types/ml_update_response_auth_method.py +5 -0
  139. label_studio_sdk/predictions/__init__.py +2 -0
  140. label_studio_sdk/predictions/client.py +638 -0
  141. label_studio_sdk/projects/__init__.py +6 -0
  142. label_studio_sdk/projects/client.py +1053 -0
  143. label_studio_sdk/projects/exports/__init__.py +2 -0
  144. label_studio_sdk/projects/exports/client.py +930 -0
  145. label_studio_sdk/projects/types/__init__.py +7 -0
  146. label_studio_sdk/projects/types/projects_create_response.py +96 -0
  147. label_studio_sdk/projects/types/projects_import_tasks_response.py +71 -0
  148. label_studio_sdk/projects/types/projects_list_response.py +33 -0
  149. label_studio_sdk/py.typed +0 -0
  150. label_studio_sdk/tasks/__init__.py +5 -0
  151. label_studio_sdk/tasks/client.py +811 -0
  152. label_studio_sdk/tasks/types/__init__.py +6 -0
  153. label_studio_sdk/tasks/types/tasks_list_request_fields.py +5 -0
  154. label_studio_sdk/tasks/types/tasks_list_response.py +48 -0
  155. label_studio_sdk/types/__init__.py +115 -0
  156. label_studio_sdk/types/annotation.py +116 -0
  157. label_studio_sdk/types/annotation_filter_options.py +42 -0
  158. label_studio_sdk/types/annotation_last_action.py +19 -0
  159. label_studio_sdk/types/azure_blob_export_storage.py +112 -0
  160. label_studio_sdk/types/azure_blob_export_storage_status.py +7 -0
  161. label_studio_sdk/types/azure_blob_import_storage.py +113 -0
  162. label_studio_sdk/types/azure_blob_import_storage_status.py +7 -0
  163. label_studio_sdk/types/base_task.py +113 -0
  164. label_studio_sdk/types/base_user.py +42 -0
  165. label_studio_sdk/types/converted_format.py +36 -0
  166. label_studio_sdk/types/converted_format_status.py +5 -0
  167. label_studio_sdk/types/export.py +48 -0
  168. label_studio_sdk/types/export_convert.py +32 -0
  169. label_studio_sdk/types/export_create.py +54 -0
  170. label_studio_sdk/types/export_create_status.py +5 -0
  171. label_studio_sdk/types/export_status.py +5 -0
  172. label_studio_sdk/types/file_upload.py +30 -0
  173. label_studio_sdk/types/filter.py +53 -0
  174. label_studio_sdk/types/filter_group.py +35 -0
  175. label_studio_sdk/types/gcs_export_storage.py +112 -0
  176. label_studio_sdk/types/gcs_export_storage_status.py +7 -0
  177. label_studio_sdk/types/gcs_import_storage.py +113 -0
  178. label_studio_sdk/types/gcs_import_storage_status.py +7 -0
  179. label_studio_sdk/types/local_files_export_storage.py +97 -0
  180. label_studio_sdk/types/local_files_export_storage_status.py +7 -0
  181. label_studio_sdk/types/local_files_import_storage.py +92 -0
  182. label_studio_sdk/types/local_files_import_storage_status.py +7 -0
  183. label_studio_sdk/types/ml_backend.py +89 -0
  184. label_studio_sdk/types/ml_backend_auth_method.py +5 -0
  185. label_studio_sdk/types/ml_backend_state.py +5 -0
  186. label_studio_sdk/types/prediction.py +78 -0
  187. label_studio_sdk/types/project.py +198 -0
  188. label_studio_sdk/types/project_import.py +63 -0
  189. label_studio_sdk/types/project_import_status.py +5 -0
  190. label_studio_sdk/types/project_label_config.py +32 -0
  191. label_studio_sdk/types/project_sampling.py +7 -0
  192. label_studio_sdk/types/project_skip_queue.py +5 -0
  193. label_studio_sdk/types/redis_export_storage.py +117 -0
  194. label_studio_sdk/types/redis_export_storage_status.py +7 -0
  195. label_studio_sdk/types/redis_import_storage.py +112 -0
  196. label_studio_sdk/types/redis_import_storage_status.py +7 -0
  197. label_studio_sdk/types/s3export_storage.py +134 -0
  198. label_studio_sdk/types/s3export_storage_status.py +7 -0
  199. label_studio_sdk/types/s3import_storage.py +140 -0
  200. label_studio_sdk/types/s3import_storage_status.py +7 -0
  201. label_studio_sdk/types/serialization_option.py +36 -0
  202. label_studio_sdk/types/serialization_options.py +45 -0
  203. label_studio_sdk/types/task.py +157 -0
  204. label_studio_sdk/types/task_filter_options.py +49 -0
  205. label_studio_sdk/types/user_simple.py +37 -0
  206. label_studio_sdk/types/view.py +55 -0
  207. label_studio_sdk/types/webhook.py +67 -0
  208. label_studio_sdk/types/webhook_actions_item.py +21 -0
  209. label_studio_sdk/types/webhook_serializer_for_update.py +67 -0
  210. label_studio_sdk/types/webhook_serializer_for_update_actions_item.py +21 -0
  211. label_studio_sdk/users/__init__.py +5 -0
  212. label_studio_sdk/users/client.py +830 -0
  213. label_studio_sdk/users/types/__init__.py +6 -0
  214. label_studio_sdk/users/types/users_get_token_response.py +36 -0
  215. label_studio_sdk/users/types/users_reset_token_response.py +36 -0
  216. label_studio_sdk/version.py +4 -0
  217. label_studio_sdk/views/__init__.py +31 -0
  218. label_studio_sdk/views/client.py +564 -0
  219. label_studio_sdk/views/types/__init__.py +29 -0
  220. label_studio_sdk/views/types/views_create_request_data.py +43 -0
  221. label_studio_sdk/views/types/views_create_request_data_filters.py +43 -0
  222. label_studio_sdk/views/types/views_create_request_data_filters_conjunction.py +5 -0
  223. label_studio_sdk/views/types/views_create_request_data_filters_items_item.py +47 -0
  224. label_studio_sdk/views/types/views_create_request_data_ordering_item.py +38 -0
  225. label_studio_sdk/views/types/views_create_request_data_ordering_item_direction.py +5 -0
  226. label_studio_sdk/views/types/views_update_request_data.py +43 -0
  227. label_studio_sdk/views/types/views_update_request_data_filters.py +43 -0
  228. label_studio_sdk/views/types/views_update_request_data_filters_conjunction.py +5 -0
  229. label_studio_sdk/views/types/views_update_request_data_filters_items_item.py +47 -0
  230. label_studio_sdk/views/types/views_update_request_data_ordering_item.py +38 -0
  231. label_studio_sdk/views/types/views_update_request_data_ordering_item_direction.py +5 -0
  232. label_studio_sdk/webhooks/__init__.py +5 -0
  233. label_studio_sdk/webhooks/client.py +636 -0
  234. label_studio_sdk/webhooks/types/__init__.py +5 -0
  235. label_studio_sdk/webhooks/types/webhooks_update_request_actions_item.py +21 -0
  236. label_studio_sdk-1.0.0.dist-info/METADATA +307 -0
  237. label_studio_sdk-1.0.0.dist-info/RECORD +239 -0
  238. {label_studio_sdk-0.0.32.dist-info → label_studio_sdk-1.0.0.dist-info}/WHEEL +1 -2
  239. docs/__init__.py +0 -3
  240. label_studio_sdk-0.0.32.dist-info/LICENSE +0 -201
  241. label_studio_sdk-0.0.32.dist-info/METADATA +0 -22
  242. label_studio_sdk-0.0.32.dist-info/RECORD +0 -15
  243. label_studio_sdk-0.0.32.dist-info/top_level.txt +0 -3
  244. tests/test_client.py +0 -26
  245. {tests → label_studio_sdk/_extensions}/__init__.py +0 -0
@@ -0,0 +1,443 @@
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
+
14
+ from .file import File, convert_file_dict_to_httpx_tuples
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
+
20
+ INITIAL_RETRY_DELAY_SECONDS = 0.5
21
+ MAX_RETRY_DELAY_SECONDS = 10
22
+ MAX_RETRY_DELAY_SECONDS_FROM_HEADER = 30
23
+
24
+
25
+ def _parse_retry_after(response_headers: httpx.Headers) -> typing.Optional[float]:
26
+ """
27
+ This function parses the `Retry-After` header in a HTTP response and returns the number of seconds to wait.
28
+
29
+ Inspired by the urllib3 retry implementation.
30
+ """
31
+ retry_after_ms = response_headers.get("retry-after-ms")
32
+ if retry_after_ms is not None:
33
+ try:
34
+ return int(retry_after_ms) / 1000 if retry_after_ms > 0 else 0
35
+ except Exception:
36
+ pass
37
+
38
+ retry_after = response_headers.get("retry-after")
39
+ if retry_after is None:
40
+ return None
41
+
42
+ # Attempt to parse the header as an int.
43
+ if re.match(r"^\s*[0-9]+\s*$", retry_after):
44
+ seconds = float(retry_after)
45
+ # Fallback to parsing it as a date.
46
+ else:
47
+ retry_date_tuple = email.utils.parsedate_tz(retry_after)
48
+ if retry_date_tuple is None:
49
+ return None
50
+ if retry_date_tuple[9] is None: # Python 2
51
+ # Assume UTC if no timezone was specified
52
+ # On Python2.7, parsedate_tz returns None for a timezone offset
53
+ # instead of 0 if no timezone is given, where mktime_tz treats
54
+ # a None timezone offset as local time.
55
+ retry_date_tuple = retry_date_tuple[:9] + (0,) + retry_date_tuple[10:]
56
+
57
+ retry_date = email.utils.mktime_tz(retry_date_tuple)
58
+ seconds = retry_date - time.time()
59
+
60
+ if seconds < 0:
61
+ seconds = 0
62
+
63
+ return seconds
64
+
65
+
66
+ def _retry_timeout(response: httpx.Response, retries: int) -> float:
67
+ """
68
+ Determine the amount of time to wait before retrying a request.
69
+ This function begins by trying to parse a retry-after header from the response, and then proceeds to use exponential backoff
70
+ with a jitter to determine the number of seconds to wait.
71
+ """
72
+
73
+ # If the API asks us to wait a certain amount of time (and it's a reasonable amount), just do what it says.
74
+ retry_after = _parse_retry_after(response.headers)
75
+ if retry_after is not None and retry_after <= MAX_RETRY_DELAY_SECONDS_FROM_HEADER:
76
+ return retry_after
77
+
78
+ # Apply exponential backoff, capped at MAX_RETRY_DELAY_SECONDS.
79
+ retry_delay = min(INITIAL_RETRY_DELAY_SECONDS * pow(2.0, retries), MAX_RETRY_DELAY_SECONDS)
80
+
81
+ # Add a randomness / jitter to the retry delay to avoid overwhelming the server with retries.
82
+ timeout = retry_delay * (1 - 0.25 * random())
83
+ return timeout if timeout >= 0 else 0
84
+
85
+
86
+ def _should_retry(response: httpx.Response) -> bool:
87
+ retriable_400s = [429, 408, 409]
88
+ return response.status_code >= 500 or response.status_code in retriable_400s
89
+
90
+
91
+ def remove_omit_from_dict(
92
+ original: typing.Dict[str, typing.Optional[typing.Any]], omit: typing.Optional[typing.Any]
93
+ ) -> typing.Dict[str, typing.Any]:
94
+ if omit is None:
95
+ return original
96
+ new: typing.Dict[str, typing.Any] = {}
97
+ for key, value in original.items():
98
+ if value is not omit:
99
+ new[key] = value
100
+ return new
101
+
102
+
103
+ def maybe_filter_request_body(
104
+ data: typing.Optional[typing.Any],
105
+ request_options: typing.Optional[RequestOptions],
106
+ omit: typing.Optional[typing.Any],
107
+ ) -> typing.Optional[typing.Any]:
108
+ if data is None:
109
+ return None
110
+ elif not isinstance(data, typing.Mapping):
111
+ data_content = jsonable_encoder(data)
112
+ else:
113
+ data_content = {
114
+ **(jsonable_encoder(remove_omit_from_dict(data, omit))), # type: ignore
115
+ **(
116
+ jsonable_encoder(request_options.get("additional_body_parameters", {}))
117
+ if request_options is not None
118
+ else {}
119
+ ),
120
+ }
121
+ return data_content
122
+
123
+
124
+ class HttpClient:
125
+ def __init__(
126
+ self,
127
+ *,
128
+ httpx_client: httpx.Client,
129
+ base_timeout: typing.Optional[float],
130
+ base_headers: typing.Dict[str, str],
131
+ base_url: typing.Optional[str] = None,
132
+ ):
133
+ self.base_url = base_url
134
+ self.base_timeout = base_timeout
135
+ self.base_headers = base_headers
136
+ self.httpx_client = httpx_client
137
+
138
+ def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str:
139
+ base_url = self.base_url if maybe_base_url is None else maybe_base_url
140
+ if base_url is None:
141
+ raise ValueError("A base_url is required to make this request, please provide one and try again.")
142
+ return base_url
143
+
144
+ def request(
145
+ self,
146
+ path: typing.Optional[str] = None,
147
+ *,
148
+ method: str,
149
+ base_url: typing.Optional[str] = None,
150
+ params: typing.Optional[typing.Dict[str, typing.Any]] = None,
151
+ json: typing.Optional[typing.Any] = None,
152
+ data: typing.Optional[typing.Any] = None,
153
+ content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None,
154
+ files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None,
155
+ headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
156
+ request_options: typing.Optional[RequestOptions] = None,
157
+ retries: int = 0,
158
+ omit: typing.Optional[typing.Any] = None,
159
+ ) -> httpx.Response:
160
+ base_url = self.get_base_url(base_url)
161
+ timeout = (
162
+ request_options.get("timeout_in_seconds")
163
+ if request_options is not None and request_options.get("timeout_in_seconds") is not None
164
+ else self.base_timeout
165
+ )
166
+
167
+ response = self.httpx_client.request(
168
+ method=method,
169
+ url=urllib.parse.urljoin(f"{base_url}/", path),
170
+ headers=jsonable_encoder(
171
+ remove_none_from_dict(
172
+ {
173
+ **self.base_headers,
174
+ **(headers if headers is not None else {}),
175
+ **(request_options.get("additional_headers", {}) if request_options is not None else {}),
176
+ }
177
+ )
178
+ ),
179
+ params=encode_query(
180
+ jsonable_encoder(
181
+ remove_none_from_dict(
182
+ remove_omit_from_dict(
183
+ {
184
+ **(params if params is not None else {}),
185
+ **(
186
+ request_options.get("additional_query_parameters", {})
187
+ if request_options is not None
188
+ else {}
189
+ ),
190
+ },
191
+ omit,
192
+ )
193
+ )
194
+ )
195
+ ),
196
+ json=maybe_filter_request_body(json, request_options, omit),
197
+ data=maybe_filter_request_body(data, request_options, omit),
198
+ content=content,
199
+ files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None,
200
+ timeout=timeout,
201
+ )
202
+
203
+ max_retries: int = request_options.get("max_retries", 0) if request_options is not None else 0
204
+ if _should_retry(response=response):
205
+ if max_retries > retries:
206
+ time.sleep(_retry_timeout(response=response, retries=retries))
207
+ return self.request(
208
+ path=path,
209
+ method=method,
210
+ base_url=base_url,
211
+ params=params,
212
+ json=json,
213
+ content=content,
214
+ files=files,
215
+ headers=headers,
216
+ request_options=request_options,
217
+ retries=retries + 1,
218
+ omit=omit,
219
+ )
220
+
221
+ return response
222
+
223
+ @contextmanager
224
+ def stream(
225
+ self,
226
+ path: typing.Optional[str] = None,
227
+ *,
228
+ method: str,
229
+ base_url: typing.Optional[str] = None,
230
+ params: typing.Optional[typing.Dict[str, typing.Any]] = None,
231
+ json: typing.Optional[typing.Any] = None,
232
+ data: typing.Optional[typing.Any] = None,
233
+ content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None,
234
+ files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None,
235
+ headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
236
+ request_options: typing.Optional[RequestOptions] = None,
237
+ retries: int = 0,
238
+ omit: typing.Optional[typing.Any] = None,
239
+ ) -> typing.Iterator[httpx.Response]:
240
+ base_url = self.get_base_url(base_url)
241
+ timeout = (
242
+ request_options.get("timeout_in_seconds")
243
+ if request_options is not None and request_options.get("timeout_in_seconds") is not None
244
+ else self.base_timeout
245
+ )
246
+
247
+ with self.httpx_client.stream(
248
+ method=method,
249
+ url=urllib.parse.urljoin(f"{base_url}/", path),
250
+ headers=jsonable_encoder(
251
+ remove_none_from_dict(
252
+ {
253
+ **self.base_headers,
254
+ **(headers if headers is not None else {}),
255
+ **(request_options.get("additional_headers", {}) if request_options is not None else {}),
256
+ }
257
+ )
258
+ ),
259
+ params=encode_query(
260
+ jsonable_encoder(
261
+ remove_none_from_dict(
262
+ remove_omit_from_dict(
263
+ {
264
+ **(params if params is not None else {}),
265
+ **(
266
+ request_options.get("additional_query_parameters", {})
267
+ if request_options is not None
268
+ else {}
269
+ ),
270
+ },
271
+ omit,
272
+ )
273
+ )
274
+ )
275
+ ),
276
+ json=maybe_filter_request_body(json, request_options, omit),
277
+ data=maybe_filter_request_body(data, request_options, omit),
278
+ content=content,
279
+ files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None,
280
+ timeout=timeout,
281
+ ) as stream:
282
+ yield stream
283
+
284
+
285
+ class AsyncHttpClient:
286
+ def __init__(
287
+ self,
288
+ *,
289
+ httpx_client: httpx.AsyncClient,
290
+ base_timeout: typing.Optional[float],
291
+ base_headers: typing.Dict[str, str],
292
+ base_url: typing.Optional[str] = None,
293
+ ):
294
+ self.base_url = base_url
295
+ self.base_timeout = base_timeout
296
+ self.base_headers = base_headers
297
+ self.httpx_client = httpx_client
298
+
299
+ def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str:
300
+ base_url = self.base_url if maybe_base_url is None else maybe_base_url
301
+ if base_url is None:
302
+ raise ValueError("A base_url is required to make this request, please provide one and try again.")
303
+ return base_url
304
+
305
+ async def request(
306
+ self,
307
+ path: typing.Optional[str] = None,
308
+ *,
309
+ method: str,
310
+ base_url: typing.Optional[str] = None,
311
+ params: typing.Optional[typing.Dict[str, typing.Any]] = None,
312
+ json: typing.Optional[typing.Any] = None,
313
+ data: typing.Optional[typing.Any] = None,
314
+ content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None,
315
+ files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None,
316
+ headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
317
+ request_options: typing.Optional[RequestOptions] = None,
318
+ retries: int = 0,
319
+ omit: typing.Optional[typing.Any] = None,
320
+ ) -> httpx.Response:
321
+ base_url = self.get_base_url(base_url)
322
+ timeout = (
323
+ request_options.get("timeout_in_seconds")
324
+ if request_options is not None and request_options.get("timeout_in_seconds") is not None
325
+ else self.base_timeout
326
+ )
327
+
328
+ # Add the input to each of these and do None-safety checks
329
+ response = await self.httpx_client.request(
330
+ method=method,
331
+ url=urllib.parse.urljoin(f"{base_url}/", path),
332
+ headers=jsonable_encoder(
333
+ remove_none_from_dict(
334
+ {
335
+ **self.base_headers,
336
+ **(headers if headers is not None else {}),
337
+ **(request_options.get("additional_headers", {}) if request_options is not None else {}),
338
+ }
339
+ )
340
+ ),
341
+ params=encode_query(
342
+ jsonable_encoder(
343
+ remove_none_from_dict(
344
+ remove_omit_from_dict(
345
+ {
346
+ **(params if params is not None else {}),
347
+ **(
348
+ request_options.get("additional_query_parameters", {})
349
+ if request_options is not None
350
+ else {}
351
+ ),
352
+ },
353
+ omit,
354
+ )
355
+ )
356
+ )
357
+ ),
358
+ json=maybe_filter_request_body(json, request_options, omit),
359
+ data=maybe_filter_request_body(data, request_options, omit),
360
+ content=content,
361
+ files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None,
362
+ timeout=timeout,
363
+ )
364
+
365
+ max_retries: int = request_options.get("max_retries", 0) if request_options is not None else 0
366
+ if _should_retry(response=response):
367
+ if max_retries > retries:
368
+ await asyncio.sleep(_retry_timeout(response=response, retries=retries))
369
+ return await self.request(
370
+ path=path,
371
+ method=method,
372
+ base_url=base_url,
373
+ params=params,
374
+ json=json,
375
+ content=content,
376
+ files=files,
377
+ headers=headers,
378
+ request_options=request_options,
379
+ retries=retries + 1,
380
+ omit=omit,
381
+ )
382
+ return response
383
+
384
+ @asynccontextmanager
385
+ async def stream(
386
+ self,
387
+ path: typing.Optional[str] = None,
388
+ *,
389
+ method: str,
390
+ base_url: typing.Optional[str] = None,
391
+ params: typing.Optional[typing.Dict[str, typing.Any]] = None,
392
+ json: typing.Optional[typing.Any] = None,
393
+ data: typing.Optional[typing.Any] = None,
394
+ content: typing.Optional[typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]] = None,
395
+ files: typing.Optional[typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]] = None,
396
+ headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
397
+ request_options: typing.Optional[RequestOptions] = None,
398
+ retries: int = 0,
399
+ omit: typing.Optional[typing.Any] = None,
400
+ ) -> typing.AsyncIterator[httpx.Response]:
401
+ base_url = self.get_base_url(base_url)
402
+ timeout = (
403
+ request_options.get("timeout_in_seconds")
404
+ if request_options is not None and request_options.get("timeout_in_seconds") is not None
405
+ else self.base_timeout
406
+ )
407
+
408
+ async with self.httpx_client.stream(
409
+ method=method,
410
+ url=urllib.parse.urljoin(f"{base_url}/", path),
411
+ headers=jsonable_encoder(
412
+ remove_none_from_dict(
413
+ {
414
+ **self.base_headers,
415
+ **(headers if headers is not None else {}),
416
+ **(request_options.get("additional_headers", {}) if request_options is not None else {}),
417
+ }
418
+ )
419
+ ),
420
+ params=encode_query(
421
+ jsonable_encoder(
422
+ remove_none_from_dict(
423
+ remove_omit_from_dict(
424
+ {
425
+ **(params if params is not None else {}),
426
+ **(
427
+ request_options.get("additional_query_parameters", {})
428
+ if request_options is not None
429
+ else {}
430
+ ),
431
+ },
432
+ omit=omit,
433
+ )
434
+ )
435
+ )
436
+ ),
437
+ json=maybe_filter_request_body(json, request_options, omit),
438
+ data=maybe_filter_request_body(data, request_options, omit),
439
+ content=content,
440
+ files=convert_file_dict_to_httpx_tuples(remove_none_from_dict(files)) if files is not None else None,
441
+ timeout=timeout,
442
+ ) as stream:
443
+ yield stream
@@ -0,0 +1,99 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ """
4
+ jsonable_encoder converts a Python object to a JSON-friendly dict
5
+ (e.g. datetimes to strings, Pydantic models to dicts).
6
+
7
+ Taken from FastAPI, and made a bit simpler
8
+ https://github.com/tiangolo/fastapi/blob/master/fastapi/encoders.py
9
+ """
10
+
11
+ import dataclasses
12
+ import datetime as dt
13
+ from collections import defaultdict
14
+ from enum import Enum
15
+ from pathlib import PurePath
16
+ from types import GeneratorType
17
+ from typing import Any, Callable, Dict, List, Optional, Set, Tuple, Union
18
+
19
+ from .datetime_utils import serialize_datetime
20
+ from .pydantic_utilities import pydantic_v1
21
+
22
+ SetIntStr = Set[Union[int, str]]
23
+ DictIntStrAny = Dict[Union[int, str], Any]
24
+
25
+
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
+ def jsonable_encoder(obj: Any, custom_encoder: Optional[Dict[Any, Callable[[Any], Any]]] = None) -> Any:
39
+ custom_encoder = custom_encoder or {}
40
+ if custom_encoder:
41
+ if type(obj) in custom_encoder:
42
+ return custom_encoder[type(obj)](obj)
43
+ else:
44
+ for encoder_type, encoder_instance in custom_encoder.items():
45
+ if isinstance(obj, encoder_type):
46
+ return encoder_instance(obj)
47
+ if isinstance(obj, pydantic_v1.BaseModel):
48
+ encoder = getattr(obj.__config__, "json_encoders", {})
49
+ if custom_encoder:
50
+ encoder.update(custom_encoder)
51
+ obj_dict = obj.dict(by_alias=True)
52
+ if "__root__" in obj_dict:
53
+ obj_dict = obj_dict["__root__"]
54
+ return jsonable_encoder(obj_dict, custom_encoder=encoder)
55
+ if dataclasses.is_dataclass(obj):
56
+ obj_dict = dataclasses.asdict(obj)
57
+ return jsonable_encoder(obj_dict, custom_encoder=custom_encoder)
58
+ if isinstance(obj, Enum):
59
+ return obj.value
60
+ if isinstance(obj, PurePath):
61
+ return str(obj)
62
+ if isinstance(obj, (str, int, float, type(None))):
63
+ return obj
64
+ if isinstance(obj, dt.datetime):
65
+ return serialize_datetime(obj)
66
+ if isinstance(obj, dt.date):
67
+ return str(obj)
68
+ if isinstance(obj, dict):
69
+ encoded_dict = {}
70
+ allowed_keys = set(obj.keys())
71
+ for key, value in obj.items():
72
+ if key in allowed_keys:
73
+ encoded_key = jsonable_encoder(key, custom_encoder=custom_encoder)
74
+ encoded_value = jsonable_encoder(value, custom_encoder=custom_encoder)
75
+ encoded_dict[encoded_key] = encoded_value
76
+ return encoded_dict
77
+ if isinstance(obj, (list, set, frozenset, GeneratorType, tuple)):
78
+ encoded_list = []
79
+ for item in obj:
80
+ encoded_list.append(jsonable_encoder(item, custom_encoder=custom_encoder))
81
+ return encoded_list
82
+
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)
88
+
89
+ try:
90
+ data = dict(obj)
91
+ except Exception as e:
92
+ errors: List[Exception] = []
93
+ errors.append(e)
94
+ try:
95
+ data = vars(obj)
96
+ except Exception as e:
97
+ errors.append(e)
98
+ raise ValueError(errors) from e
99
+ return jsonable_encoder(data, custom_encoder=custom_encoder)
@@ -0,0 +1,87 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ import typing
4
+
5
+ from typing_extensions import Self
6
+
7
+ from .pydantic_utilities import pydantic_v1
8
+
9
+ # Generic to represent the underlying type of the results within a page
10
+ T = typing.TypeVar("T")
11
+
12
+ # SDKs implement a Page ABC per-pagination request, the endpoint then retuns a pager that wraps this type
13
+ # for example, an endpoint will return SyncPager[UserPage] where UserPage implements the Page ABC. ex:
14
+ #
15
+ # SyncPager<InnerListType>(
16
+ # has_next=response.list_metadata.after is not None,
17
+ # items=response.data,
18
+ # # This should be the outer function that returns the SyncPager again
19
+ # get_next=lambda: list(..., cursor: response.cursor) (or list(..., offset: offset + 1))
20
+ # )
21
+ class BasePage(pydantic_v1.BaseModel, typing.Generic[T]):
22
+ has_next: bool
23
+ items: typing.Optional[typing.List[T]]
24
+
25
+
26
+ class SyncPage(BasePage, typing.Generic[T]):
27
+ get_next: typing.Optional[typing.Callable[[], typing.Optional[Self]]]
28
+
29
+
30
+ class AsyncPage(BasePage, typing.Generic[T]):
31
+ get_next: typing.Optional[typing.Callable[[], typing.Awaitable[typing.Optional[Self]]]]
32
+
33
+
34
+ # ----------------------------
35
+
36
+
37
+ class SyncPager(SyncPage[T], typing.Generic[T]):
38
+ # Here we type ignore the iterator to avoid a mypy error
39
+ # caused by the type conflict with Pydanitc's __iter__ method
40
+ # brought in by extending the base model
41
+ def __iter__(self) -> typing.Iterator[T]: # type: ignore
42
+ for page in self.iter_pages():
43
+ if page.items is not None:
44
+ for item in page.items:
45
+ yield item
46
+
47
+ def iter_pages(self) -> typing.Iterator[SyncPage[T]]:
48
+ page: typing.Union[SyncPager[T], None] = self
49
+ while True:
50
+ if page is not None:
51
+ yield page
52
+ if page.has_next and page.get_next is not None:
53
+ page = page.get_next()
54
+ if page is None or page.items is None or len(page.items) == 0:
55
+ return
56
+ else:
57
+ return
58
+ else:
59
+ return
60
+
61
+ def next_page(self) -> typing.Optional[SyncPage[T]]:
62
+ return self.get_next() if self.get_next is not None else None
63
+
64
+
65
+ class AsyncPager(AsyncPage[T], typing.Generic[T]):
66
+ async def __aiter__(self) -> typing.AsyncIterator[T]: # type: ignore
67
+ async for page in self.iter_pages():
68
+ if page.items is not None:
69
+ for item in page.items:
70
+ yield item
71
+
72
+ async def iter_pages(self) -> typing.AsyncIterator[AsyncPage[T]]:
73
+ page: typing.Union[AsyncPager[T], None] = self
74
+ while True:
75
+ if page is not None:
76
+ yield page
77
+ if page is not None and page.has_next and page.get_next is not None:
78
+ page = await page.get_next()
79
+ if page is None or page.items is None or len(page.items) == 0:
80
+ return
81
+ else:
82
+ return
83
+ else:
84
+ return
85
+
86
+ async def next_page(self) -> typing.Optional[AsyncPage[T]]:
87
+ return await self.get_next() if self.get_next is not None else None
@@ -0,0 +1,28 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ import typing
4
+
5
+ import pydantic
6
+
7
+ IS_PYDANTIC_V2 = pydantic.VERSION.startswith("2.")
8
+
9
+ if IS_PYDANTIC_V2:
10
+ import pydantic.v1 as pydantic_v1 # type: ignore # nopycln: import
11
+ else:
12
+ import pydantic as pydantic_v1 # type: ignore # nopycln: import
13
+
14
+
15
+ def deep_union_pydantic_dicts(
16
+ source: typing.Dict[str, typing.Any], destination: typing.Dict[str, typing.Any]
17
+ ) -> typing.Dict[str, typing.Any]:
18
+ for key, value in source.items():
19
+ if isinstance(value, dict):
20
+ node = destination.setdefault(key, {})
21
+ deep_union_pydantic_dicts(value, node)
22
+ else:
23
+ destination[key] = value
24
+
25
+ return destination
26
+
27
+
28
+ __all__ = ["pydantic_v1"]
@@ -0,0 +1,33 @@
1
+ # This file was auto-generated by Fern from our API Definition.
2
+
3
+ from collections import ChainMap
4
+ from typing import Any, Dict, Optional
5
+
6
+ from .pydantic_utilities import pydantic_v1
7
+
8
+
9
+ # Flattens dicts to be of the form {"key[subkey][subkey2]": value} where value is not a dict
10
+ def traverse_query_dict(dict_flat: Dict[str, Any], key_prefix: Optional[str] = None) -> Dict[str, Any]:
11
+ result = {}
12
+ for k, v in dict_flat.items():
13
+ key = f"{key_prefix}[{k}]" if key_prefix is not None else k
14
+ if isinstance(v, dict):
15
+ result.update(traverse_query_dict(v, key))
16
+ else:
17
+ result[key] = v
18
+ return result
19
+
20
+
21
+ def single_query_encoder(query_key: str, query_value: Any) -> Dict[str, Any]:
22
+ if isinstance(query_value, pydantic_v1.BaseModel) or isinstance(query_value, dict):
23
+ if isinstance(query_value, pydantic_v1.BaseModel):
24
+ obj_dict = query_value.dict(by_alias=True)
25
+ else:
26
+ obj_dict = query_value
27
+ return traverse_query_dict(obj_dict, query_key)
28
+
29
+ return {query_key: query_value}
30
+
31
+
32
+ def encode_query(query: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
33
+ return dict(ChainMap(*[single_query_encoder(k, v) for k, v in query.items()])) if query is not None else None