label-studio-sdk 1.0.8__py3-none-any.whl → 1.0.11__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.
- label_studio_sdk/__init__.py +37 -8
- label_studio_sdk/_extensions/label_studio_tools/core/utils/io.py +16 -4
- label_studio_sdk/_extensions/label_studio_tools/core/utils/json_schema.py +5 -0
- label_studio_sdk/_extensions/pager_ext.py +8 -0
- label_studio_sdk/actions/client.py +91 -40
- label_studio_sdk/actions/types/actions_create_request_filters.py +14 -24
- label_studio_sdk/actions/types/actions_create_request_filters_items_item.py +16 -26
- label_studio_sdk/actions/types/actions_create_request_filters_items_item_value.py +3 -1
- label_studio_sdk/actions/types/actions_create_request_selected_items.py +1 -2
- label_studio_sdk/actions/types/actions_create_request_selected_items_excluded.py +15 -25
- label_studio_sdk/actions/types/actions_create_request_selected_items_included.py +15 -25
- label_studio_sdk/annotations/__init__.py +2 -2
- label_studio_sdk/annotations/client.py +278 -104
- label_studio_sdk/annotations/types/__init__.py +2 -1
- label_studio_sdk/annotations/types/annotations_create_bulk_request_selected_items.py +34 -0
- label_studio_sdk/annotations/types/annotations_create_bulk_response_item.py +11 -21
- label_studio_sdk/base_client.py +54 -27
- label_studio_sdk/client.py +1 -0
- label_studio_sdk/comments/client.py +190 -44
- label_studio_sdk/converter/converter.py +56 -13
- label_studio_sdk/converter/imports/yolo.py +1 -1
- label_studio_sdk/converter/utils.py +3 -2
- label_studio_sdk/core/__init__.py +21 -4
- label_studio_sdk/core/client_wrapper.py +37 -19
- label_studio_sdk/core/file.py +37 -8
- label_studio_sdk/core/http_client.py +52 -28
- label_studio_sdk/core/jsonable_encoder.py +33 -31
- label_studio_sdk/core/pagination.py +5 -4
- label_studio_sdk/core/pydantic_utilities.py +272 -4
- label_studio_sdk/core/query_encoder.py +38 -13
- label_studio_sdk/core/request_options.py +3 -0
- label_studio_sdk/core/serialization.py +272 -0
- label_studio_sdk/errors/__init__.py +3 -1
- label_studio_sdk/errors/bad_request_error.py +2 -3
- label_studio_sdk/errors/not_found_error.py +9 -0
- label_studio_sdk/errors/unauthorized_error.py +9 -0
- label_studio_sdk/export_storage/azure/client.py +228 -58
- label_studio_sdk/export_storage/azure/types/azure_create_response.py +19 -29
- label_studio_sdk/export_storage/azure/types/azure_update_response.py +19 -29
- label_studio_sdk/export_storage/client.py +48 -18
- label_studio_sdk/export_storage/gcs/client.py +228 -58
- label_studio_sdk/export_storage/gcs/types/gcs_create_response.py +19 -29
- label_studio_sdk/export_storage/gcs/types/gcs_update_response.py +19 -29
- label_studio_sdk/export_storage/local/client.py +222 -56
- label_studio_sdk/export_storage/local/types/local_create_response.py +17 -27
- label_studio_sdk/export_storage/local/types/local_update_response.py +17 -27
- label_studio_sdk/export_storage/redis/client.py +228 -58
- label_studio_sdk/export_storage/redis/types/redis_create_response.py +20 -30
- label_studio_sdk/export_storage/redis/types/redis_update_response.py +20 -30
- label_studio_sdk/export_storage/s3/client.py +228 -58
- label_studio_sdk/export_storage/s3/types/s3create_response.py +27 -35
- label_studio_sdk/export_storage/s3/types/s3update_response.py +27 -35
- label_studio_sdk/export_storage/s3s/client.py +187 -43
- label_studio_sdk/export_storage/types/export_storage_list_types_response_item.py +11 -21
- label_studio_sdk/files/client.py +172 -56
- label_studio_sdk/import_storage/azure/client.py +223 -53
- label_studio_sdk/import_storage/azure/types/azure_create_response.py +22 -32
- label_studio_sdk/import_storage/azure/types/azure_update_response.py +22 -32
- label_studio_sdk/import_storage/client.py +48 -18
- label_studio_sdk/import_storage/gcs/client.py +223 -53
- label_studio_sdk/import_storage/gcs/types/gcs_create_response.py +22 -32
- label_studio_sdk/import_storage/gcs/types/gcs_update_response.py +22 -32
- label_studio_sdk/import_storage/local/client.py +223 -53
- label_studio_sdk/import_storage/local/types/local_create_response.py +17 -27
- label_studio_sdk/import_storage/local/types/local_update_response.py +17 -27
- label_studio_sdk/import_storage/redis/client.py +223 -53
- label_studio_sdk/import_storage/redis/types/redis_create_response.py +20 -30
- label_studio_sdk/import_storage/redis/types/redis_update_response.py +20 -30
- label_studio_sdk/import_storage/s3/client.py +223 -53
- label_studio_sdk/import_storage/s3/types/s3create_response.py +31 -39
- label_studio_sdk/import_storage/s3/types/s3update_response.py +31 -39
- label_studio_sdk/import_storage/s3s/client.py +222 -52
- label_studio_sdk/import_storage/types/import_storage_list_types_response_item.py +11 -21
- label_studio_sdk/jwt_settings/__init__.py +2 -0
- label_studio_sdk/jwt_settings/client.py +259 -0
- label_studio_sdk/label_interface/control_tags.py +16 -3
- label_studio_sdk/label_interface/interface.py +80 -1
- label_studio_sdk/label_interface/object_tags.py +2 -2
- label_studio_sdk/ml/client.py +280 -78
- label_studio_sdk/ml/types/ml_create_response.py +21 -31
- label_studio_sdk/ml/types/ml_update_response.py +21 -31
- label_studio_sdk/model_providers/client.py +173 -56
- label_studio_sdk/predictions/client.py +247 -101
- label_studio_sdk/projects/__init__.py +5 -1
- label_studio_sdk/projects/client.py +313 -115
- label_studio_sdk/projects/client_ext.py +16 -0
- label_studio_sdk/projects/exports/__init__.py +3 -0
- label_studio_sdk/projects/exports/client.py +447 -296
- label_studio_sdk/projects/exports/client_ext.py +200 -0
- label_studio_sdk/projects/exports/types/__init__.py +6 -0
- label_studio_sdk/projects/exports/types/exports_convert_response.py +24 -0
- label_studio_sdk/projects/exports/types/exports_list_formats_response_item.py +44 -0
- label_studio_sdk/projects/pauses/__init__.py +2 -0
- label_studio_sdk/projects/pauses/client.py +704 -0
- label_studio_sdk/projects/types/projects_create_response.py +29 -34
- label_studio_sdk/projects/types/projects_import_tasks_response.py +19 -29
- label_studio_sdk/projects/types/projects_list_response.py +11 -21
- label_studio_sdk/projects/types/projects_update_response.py +34 -34
- label_studio_sdk/prompts/client.py +309 -92
- label_studio_sdk/prompts/indicators/client.py +67 -23
- label_studio_sdk/prompts/runs/client.py +95 -40
- label_studio_sdk/prompts/types/prompts_batch_failed_predictions_request_failed_predictions_item.py +14 -24
- label_studio_sdk/prompts/types/prompts_batch_failed_predictions_response.py +11 -21
- label_studio_sdk/prompts/types/prompts_batch_predictions_request_results_item.py +26 -29
- label_studio_sdk/prompts/types/prompts_batch_predictions_response.py +11 -21
- label_studio_sdk/prompts/versions/client.py +277 -88
- label_studio_sdk/tasks/client.py +263 -90
- label_studio_sdk/tasks/types/tasks_list_response.py +15 -25
- label_studio_sdk/tokens/__init__.py +2 -0
- label_studio_sdk/tokens/client.py +470 -0
- label_studio_sdk/tokens/client_ext.py +94 -0
- label_studio_sdk/types/__init__.py +20 -6
- label_studio_sdk/types/access_token_response.py +22 -0
- label_studio_sdk/types/annotation.py +29 -38
- label_studio_sdk/types/annotation_filter_options.py +14 -24
- label_studio_sdk/types/annotations_dm_field.py +30 -39
- label_studio_sdk/types/api_token_response.py +32 -0
- label_studio_sdk/types/azure_blob_export_storage.py +28 -37
- label_studio_sdk/types/azure_blob_import_storage.py +28 -37
- label_studio_sdk/types/base_task.py +30 -39
- label_studio_sdk/types/base_task_updated_by.py +3 -1
- label_studio_sdk/types/base_user.py +14 -21
- label_studio_sdk/types/comment.py +12 -21
- label_studio_sdk/types/comment_created_by.py +1 -1
- label_studio_sdk/types/converted_format.py +12 -22
- label_studio_sdk/types/data_manager_task_serializer.py +31 -40
- label_studio_sdk/types/data_manager_task_serializer_annotators_item.py +1 -1
- label_studio_sdk/types/data_manager_task_serializer_drafts_item.py +13 -22
- label_studio_sdk/types/data_manager_task_serializer_predictions_item.py +15 -24
- label_studio_sdk/types/export.py +17 -26
- label_studio_sdk/types/export_format.py +25 -0
- label_studio_sdk/types/export_snapshot.py +45 -0
- label_studio_sdk/types/export_snapshot_status.py +5 -0
- label_studio_sdk/types/file_upload.py +11 -21
- label_studio_sdk/types/filter.py +16 -26
- label_studio_sdk/types/filter_group.py +12 -22
- label_studio_sdk/types/gcs_export_storage.py +28 -37
- label_studio_sdk/types/gcs_import_storage.py +28 -37
- label_studio_sdk/types/inference_run.py +14 -23
- label_studio_sdk/types/inference_run_cost_estimate.py +17 -27
- label_studio_sdk/types/inference_run_created_by.py +1 -1
- label_studio_sdk/types/inference_run_organization.py +1 -1
- label_studio_sdk/types/jwt_settings_response.py +32 -0
- label_studio_sdk/types/key_indicator_value.py +12 -22
- label_studio_sdk/types/key_indicators.py +0 -1
- label_studio_sdk/types/key_indicators_item.py +15 -25
- label_studio_sdk/types/key_indicators_item_additional_kpis_item.py +13 -23
- label_studio_sdk/types/key_indicators_item_extra_kpis_item.py +13 -23
- label_studio_sdk/types/local_files_export_storage.py +25 -34
- label_studio_sdk/types/local_files_import_storage.py +24 -33
- label_studio_sdk/types/ml_backend.py +23 -32
- label_studio_sdk/types/model_provider_connection.py +22 -31
- label_studio_sdk/types/model_provider_connection_created_by.py +1 -1
- label_studio_sdk/types/model_provider_connection_organization.py +1 -1
- label_studio_sdk/types/model_provider_connection_provider.py +3 -1
- label_studio_sdk/types/pause.py +34 -0
- label_studio_sdk/types/pause_paused_by.py +5 -0
- label_studio_sdk/types/prediction.py +21 -30
- label_studio_sdk/types/project.py +58 -55
- label_studio_sdk/types/project_import.py +21 -30
- label_studio_sdk/types/project_label_config.py +12 -22
- label_studio_sdk/types/prompt.py +24 -32
- label_studio_sdk/types/prompt_associated_projects_item.py +6 -0
- label_studio_sdk/types/prompt_associated_projects_item_id.py +20 -0
- label_studio_sdk/types/prompt_created_by.py +1 -1
- label_studio_sdk/types/prompt_organization.py +1 -1
- label_studio_sdk/types/prompt_version.py +13 -22
- label_studio_sdk/types/prompt_version_created_by.py +1 -1
- label_studio_sdk/types/prompt_version_organization.py +1 -1
- label_studio_sdk/types/prompt_version_provider.py +3 -1
- label_studio_sdk/types/redis_export_storage.py +29 -38
- label_studio_sdk/types/redis_import_storage.py +28 -37
- label_studio_sdk/types/refined_prompt_response.py +19 -29
- label_studio_sdk/types/s3export_storage.py +36 -43
- label_studio_sdk/types/s3import_storage.py +37 -44
- label_studio_sdk/types/s3s_export_storage.py +26 -33
- label_studio_sdk/types/s3s_import_storage.py +35 -42
- label_studio_sdk/types/serialization_option.py +12 -22
- label_studio_sdk/types/serialization_options.py +18 -28
- label_studio_sdk/types/task.py +44 -47
- label_studio_sdk/types/task_annotators_item.py +1 -1
- label_studio_sdk/types/task_comment_authors_item.py +1 -1
- label_studio_sdk/types/task_filter_options.py +15 -25
- label_studio_sdk/types/user_simple.py +11 -21
- label_studio_sdk/types/view.py +16 -26
- label_studio_sdk/types/webhook.py +19 -28
- label_studio_sdk/types/webhook_serializer_for_update.py +19 -28
- label_studio_sdk/types/workspace.py +22 -31
- label_studio_sdk/users/client.py +257 -63
- label_studio_sdk/users/types/users_get_token_response.py +12 -22
- label_studio_sdk/users/types/users_reset_token_response.py +12 -22
- label_studio_sdk/version.py +0 -1
- label_studio_sdk/versions/__init__.py +5 -0
- label_studio_sdk/versions/client.py +112 -0
- label_studio_sdk/versions/types/__init__.py +6 -0
- label_studio_sdk/versions/types/versions_get_response.py +73 -0
- label_studio_sdk/versions/types/versions_get_response_edition.py +5 -0
- label_studio_sdk/views/client.py +219 -52
- label_studio_sdk/views/types/views_create_request_data.py +13 -23
- label_studio_sdk/views/types/views_create_request_data_filters.py +14 -24
- label_studio_sdk/views/types/views_create_request_data_filters_items_item.py +16 -26
- label_studio_sdk/views/types/views_create_request_data_filters_items_item_value.py +3 -1
- label_studio_sdk/views/types/views_update_request_data.py +13 -23
- label_studio_sdk/views/types/views_update_request_data_filters.py +14 -24
- label_studio_sdk/views/types/views_update_request_data_filters_items_item.py +16 -26
- label_studio_sdk/views/types/views_update_request_data_filters_items_item_value.py +3 -1
- label_studio_sdk/webhooks/client.py +191 -61
- label_studio_sdk/workspaces/client.py +164 -41
- label_studio_sdk/workspaces/members/client.py +109 -31
- label_studio_sdk/workspaces/members/types/members_create_response.py +12 -22
- label_studio_sdk/workspaces/members/types/members_list_response_item.py +12 -22
- {label_studio_sdk-1.0.8.dist-info → label_studio_sdk-1.0.11.dist-info}/METADATA +8 -5
- {label_studio_sdk-1.0.8.dist-info → label_studio_sdk-1.0.11.dist-info}/RECORD +215 -188
- {label_studio_sdk-1.0.8.dist-info → label_studio_sdk-1.0.11.dist-info}/WHEEL +1 -1
- label_studio_sdk/types/export_convert.py +0 -32
- label_studio_sdk/types/export_create.py +0 -54
- label_studio_sdk/types/export_create_status.py +0 -5
- {label_studio_sdk-1.0.8.dist-info → label_studio_sdk-1.0.11.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 =
|
|
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 =
|
|
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
|
|
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
|
-
"
|
|
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,5 +1,3 @@
|
|
|
1
|
-
# This file was auto-generated by Fern from our API Definition.
|
|
2
|
-
|
|
3
1
|
import typing
|
|
4
2
|
|
|
5
3
|
import httpx
|
|
@@ -8,37 +6,57 @@ from .http_client import AsyncHttpClient, HttpClient
|
|
|
8
6
|
|
|
9
7
|
|
|
10
8
|
class BaseClientWrapper:
|
|
11
|
-
def __init__(
|
|
12
|
-
self
|
|
9
|
+
def __init__(
|
|
10
|
+
self,
|
|
11
|
+
*,
|
|
12
|
+
api_key: str,
|
|
13
|
+
base_url: str,
|
|
14
|
+
timeout: typing.Optional[float] = None,
|
|
15
|
+
):
|
|
13
16
|
self._base_url = base_url
|
|
14
17
|
self._timeout = timeout
|
|
15
18
|
|
|
19
|
+
# even in the async case, refreshing access token (when the existing one is expired) should be sync
|
|
20
|
+
from ..tokens.client_ext import TokensClientExt
|
|
21
|
+
self._tokens_client = TokensClientExt(base_url=base_url, api_key=api_key)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def get_timeout(self) -> typing.Optional[float]:
|
|
25
|
+
return self._timeout
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_base_url(self) -> str:
|
|
29
|
+
return self._base_url
|
|
30
|
+
|
|
31
|
+
|
|
16
32
|
def get_headers(self) -> typing.Dict[str, str]:
|
|
17
33
|
headers: typing.Dict[str, str] = {
|
|
18
34
|
"X-Fern-Language": "Python",
|
|
19
35
|
"X-Fern-SDK-Name": "label-studio-sdk",
|
|
20
|
-
"X-Fern-SDK-Version": "1.0.
|
|
36
|
+
"X-Fern-SDK-Version": "1.0.11",
|
|
21
37
|
}
|
|
22
|
-
|
|
38
|
+
if self._tokens_client._use_legacy_token:
|
|
39
|
+
headers["Authorization"] = f"Token {self._tokens_client.api_key}"
|
|
40
|
+
else:
|
|
41
|
+
headers["Authorization"] = f"Bearer {self._tokens_client.api_key}"
|
|
23
42
|
return headers
|
|
24
43
|
|
|
25
|
-
def get_base_url(self) -> str:
|
|
26
|
-
return self._base_url
|
|
27
|
-
|
|
28
|
-
def get_timeout(self) -> typing.Optional[float]:
|
|
29
|
-
return self._timeout
|
|
30
|
-
|
|
31
44
|
|
|
32
45
|
class SyncClientWrapper(BaseClientWrapper):
|
|
33
46
|
def __init__(
|
|
34
|
-
self,
|
|
47
|
+
self,
|
|
48
|
+
*,
|
|
49
|
+
api_key: str,
|
|
50
|
+
base_url: str,
|
|
51
|
+
timeout: typing.Optional[float] = None,
|
|
52
|
+
httpx_client: httpx.Client,
|
|
35
53
|
):
|
|
36
54
|
super().__init__(api_key=api_key, base_url=base_url, timeout=timeout)
|
|
37
55
|
self.httpx_client = HttpClient(
|
|
38
56
|
httpx_client=httpx_client,
|
|
39
|
-
base_headers=self.get_headers
|
|
40
|
-
base_timeout=self.get_timeout
|
|
41
|
-
base_url=self.get_base_url
|
|
57
|
+
base_headers=self.get_headers,
|
|
58
|
+
base_timeout=self.get_timeout,
|
|
59
|
+
base_url=self.get_base_url,
|
|
42
60
|
)
|
|
43
61
|
|
|
44
62
|
|
|
@@ -49,7 +67,7 @@ class AsyncClientWrapper(BaseClientWrapper):
|
|
|
49
67
|
super().__init__(api_key=api_key, base_url=base_url, timeout=timeout)
|
|
50
68
|
self.httpx_client = AsyncHttpClient(
|
|
51
69
|
httpx_client=httpx_client,
|
|
52
|
-
base_headers=self.get_headers
|
|
53
|
-
base_timeout=self.get_timeout
|
|
54
|
-
base_url=self.get_base_url
|
|
70
|
+
base_headers=self.get_headers,
|
|
71
|
+
base_timeout=self.get_timeout,
|
|
72
|
+
base_url=self.get_base_url,
|
|
55
73
|
)
|
label_studio_sdk/core/file.py
CHANGED
|
@@ -1,25 +1,30 @@
|
|
|
1
1
|
# This file was auto-generated by Fern from our API Definition.
|
|
2
2
|
|
|
3
|
-
import
|
|
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 =
|
|
8
|
-
File =
|
|
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
|
-
|
|
12
|
+
Tuple[Optional[str], FileContent],
|
|
13
13
|
# (filename, file (or bytes), content_type)
|
|
14
|
-
|
|
14
|
+
Tuple[Optional[str], FileContent, Optional[str]],
|
|
15
15
|
# (filename, file (or bytes), content_type, headers)
|
|
16
|
-
|
|
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:
|
|
22
|
-
) ->
|
|
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]],
|
|
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
|
-
|
|
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 =
|
|
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=
|
|
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=
|
|
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 =
|
|
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=
|
|
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=
|
|
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,
|
|
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
|
|
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,
|
|
48
|
-
|
|
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
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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 =
|
|
90
|
+
data = dict(o)
|
|
96
91
|
except Exception as e:
|
|
92
|
+
errors: List[Exception] = []
|
|
97
93
|
errors.append(e)
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
|