agenta 0.12.3__py3-none-any.whl → 0.32.0a1__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 agenta might be problematic. Click here for more details.
- agenta/__init__.py +64 -7
- agenta/cli/helper.py +7 -3
- agenta/cli/main.py +15 -50
- agenta/cli/variant_commands.py +50 -29
- agenta/client/Readme.md +72 -64
- agenta/client/api.py +2 -2
- agenta/client/backend/__init__.py +193 -22
- agenta/client/backend/access_control/__init__.py +1 -0
- agenta/client/backend/access_control/client.py +167 -0
- agenta/client/backend/apps/__init__.py +1 -0
- agenta/client/backend/apps/client.py +1691 -0
- agenta/client/backend/bases/__init__.py +1 -0
- agenta/client/backend/bases/client.py +190 -0
- agenta/client/backend/client.py +2508 -5712
- agenta/client/backend/configs/__init__.py +1 -0
- agenta/client/backend/configs/client.py +604 -0
- agenta/client/backend/containers/__init__.py +5 -0
- agenta/client/backend/containers/client.py +648 -0
- agenta/client/backend/containers/types/__init__.py +5 -0
- agenta/client/backend/{types → containers/types}/container_templates_response.py +1 -2
- agenta/client/backend/core/__init__.py +30 -0
- agenta/client/backend/core/client_wrapper.py +42 -9
- agenta/client/backend/core/file.py +70 -0
- agenta/client/backend/core/http_client.py +575 -0
- agenta/client/backend/core/jsonable_encoder.py +33 -39
- agenta/client/backend/core/pydantic_utilities.py +325 -0
- agenta/client/backend/core/query_encoder.py +60 -0
- agenta/client/backend/core/remove_none_from_dict.py +2 -2
- agenta/client/backend/core/request_options.py +35 -0
- agenta/client/backend/core/serialization.py +276 -0
- agenta/client/backend/environments/__init__.py +1 -0
- agenta/client/backend/environments/client.py +196 -0
- agenta/client/backend/evaluations/__init__.py +1 -0
- agenta/client/backend/evaluations/client.py +1469 -0
- agenta/client/backend/evaluators/__init__.py +1 -0
- agenta/client/backend/evaluators/client.py +1283 -0
- agenta/client/backend/observability/__init__.py +1 -0
- agenta/client/backend/observability/client.py +1286 -0
- agenta/client/backend/observability_v_1/__init__.py +5 -0
- agenta/client/backend/observability_v_1/client.py +763 -0
- agenta/client/backend/observability_v_1/types/__init__.py +7 -0
- agenta/client/backend/observability_v_1/types/format.py +5 -0
- agenta/client/backend/observability_v_1/types/query_analytics_response.py +7 -0
- agenta/client/backend/observability_v_1/types/query_traces_response.py +11 -0
- agenta/client/backend/scopes/__init__.py +1 -0
- agenta/client/backend/scopes/client.py +114 -0
- agenta/client/backend/testsets/__init__.py +1 -0
- agenta/client/backend/testsets/client.py +1284 -0
- agenta/client/backend/types/__init__.py +154 -26
- agenta/client/backend/types/agenta_node_dto.py +48 -0
- agenta/client/backend/types/agenta_node_dto_nodes_value.py +6 -0
- agenta/client/backend/types/agenta_nodes_response.py +30 -0
- agenta/client/backend/types/agenta_root_dto.py +30 -0
- agenta/client/backend/types/agenta_roots_response.py +30 -0
- agenta/client/backend/types/agenta_tree_dto.py +30 -0
- agenta/client/backend/types/agenta_trees_response.py +30 -0
- agenta/client/backend/types/aggregated_result.py +16 -31
- agenta/client/backend/types/aggregated_result_evaluator_config.py +8 -0
- agenta/client/backend/types/analytics_response.py +24 -0
- agenta/client/backend/types/app.py +17 -30
- agenta/client/backend/types/app_variant_response.py +36 -0
- agenta/client/backend/types/app_variant_revision.py +17 -32
- agenta/client/backend/types/base_output.py +13 -28
- agenta/client/backend/types/body_import_testset.py +15 -31
- agenta/client/backend/types/bucket_dto.py +26 -0
- agenta/client/backend/types/collect_status_response.py +22 -0
- agenta/client/backend/types/config_db.py +16 -31
- agenta/client/backend/types/config_dto.py +32 -0
- agenta/client/backend/types/config_response_model.py +32 -0
- agenta/client/backend/types/correct_answer.py +22 -0
- agenta/client/backend/types/create_app_output.py +13 -28
- agenta/client/backend/types/create_span.py +45 -0
- agenta/client/backend/types/create_trace_response.py +22 -0
- agenta/client/backend/types/docker_env_vars.py +13 -28
- agenta/client/backend/types/environment_output.py +22 -34
- agenta/client/backend/types/environment_output_extended.py +31 -0
- agenta/client/backend/types/environment_revision.py +26 -0
- agenta/client/backend/types/error.py +22 -0
- agenta/client/backend/types/evaluation.py +22 -33
- agenta/client/backend/types/evaluation_scenario.py +18 -33
- agenta/client/backend/types/evaluation_scenario_input.py +16 -31
- agenta/client/backend/types/evaluation_scenario_output.py +17 -30
- agenta/client/backend/types/evaluation_scenario_result.py +14 -29
- agenta/client/backend/types/evaluation_scenario_score_update.py +21 -0
- agenta/client/backend/types/evaluation_status_enum.py +11 -29
- agenta/client/backend/types/evaluation_type.py +3 -21
- agenta/client/backend/types/evaluator.py +20 -31
- agenta/client/backend/types/evaluator_config.py +21 -33
- agenta/client/backend/types/evaluator_mapping_output_interface.py +21 -0
- agenta/client/backend/types/evaluator_output_interface.py +21 -0
- agenta/client/backend/types/exception_dto.py +26 -0
- agenta/client/backend/types/get_config_response.py +23 -0
- agenta/client/backend/types/header_dto.py +22 -0
- agenta/client/backend/types/http_validation_error.py +14 -29
- agenta/client/backend/types/human_evaluation.py +18 -34
- agenta/client/backend/types/human_evaluation_scenario.py +22 -38
- agenta/client/backend/types/human_evaluation_scenario_input.py +13 -28
- agenta/client/backend/types/human_evaluation_scenario_output.py +13 -28
- agenta/client/backend/types/human_evaluation_scenario_update.py +30 -0
- agenta/client/backend/types/human_evaluation_update.py +22 -0
- agenta/client/backend/types/image.py +18 -32
- agenta/client/backend/types/invite_request.py +16 -30
- agenta/client/backend/types/legacy_analytics_response.py +29 -0
- agenta/client/backend/types/legacy_data_point.py +27 -0
- agenta/client/backend/types/lifecycle_dto.py +24 -0
- agenta/client/backend/types/link_dto.py +24 -0
- agenta/client/backend/types/list_api_keys_response.py +24 -0
- agenta/client/backend/types/llm_run_rate_limit.py +13 -28
- agenta/client/backend/types/llm_tokens.py +23 -0
- agenta/client/backend/types/metrics_dto.py +24 -0
- agenta/client/backend/types/new_human_evaluation.py +27 -0
- agenta/client/backend/types/new_testset.py +16 -31
- agenta/client/backend/types/node_dto.py +24 -0
- agenta/client/backend/types/node_type.py +19 -0
- agenta/client/backend/types/o_tel_context_dto.py +22 -0
- agenta/client/backend/types/o_tel_event_dto.py +23 -0
- agenta/client/backend/types/o_tel_extra_dto.py +26 -0
- agenta/client/backend/types/o_tel_link_dto.py +23 -0
- agenta/client/backend/types/o_tel_span_dto.py +37 -0
- agenta/client/backend/types/o_tel_span_kind.py +15 -0
- agenta/client/backend/types/o_tel_spans_response.py +24 -0
- agenta/client/backend/types/o_tel_status_code.py +8 -0
- agenta/client/backend/types/organization.py +22 -35
- agenta/client/backend/types/organization_output.py +13 -28
- agenta/client/backend/types/outputs.py +5 -0
- agenta/client/backend/types/parent_dto.py +21 -0
- agenta/client/backend/types/permission.py +41 -0
- agenta/client/backend/types/projects_response.py +28 -0
- agenta/client/backend/types/provider_key_dto.py +23 -0
- agenta/client/backend/types/provider_kind.py +21 -0
- agenta/client/backend/types/reference_dto.py +23 -0
- agenta/client/backend/types/reference_request_model.py +23 -0
- agenta/client/backend/types/result.py +18 -31
- agenta/client/backend/types/root_dto.py +21 -0
- agenta/client/backend/types/{human_evaluation_scenario_score.py → score.py} +1 -1
- agenta/client/backend/types/secret_dto.py +24 -0
- agenta/client/backend/types/{human_evaluation_scenario_update_score.py → secret_kind.py} +1 -1
- agenta/client/backend/types/secret_response_dto.py +27 -0
- agenta/client/backend/types/simple_evaluation_output.py +13 -28
- agenta/client/backend/types/span.py +39 -49
- agenta/client/backend/types/span_detail.py +44 -0
- agenta/client/backend/types/span_dto.py +54 -0
- agenta/client/backend/types/span_dto_nodes_value.py +9 -0
- agenta/client/backend/types/span_status_code.py +5 -0
- agenta/client/backend/types/span_variant.py +23 -0
- agenta/client/backend/types/status_code.py +5 -0
- agenta/client/backend/types/status_dto.py +23 -0
- agenta/client/backend/types/template.py +14 -29
- agenta/client/backend/types/template_image_info.py +21 -35
- agenta/client/backend/types/test_set_output_response.py +20 -33
- agenta/client/backend/types/test_set_simple_response.py +13 -28
- agenta/client/backend/types/time_dto.py +23 -0
- agenta/client/backend/types/trace_detail.py +44 -0
- agenta/client/backend/types/tree_dto.py +23 -0
- agenta/client/backend/types/tree_type.py +5 -0
- agenta/client/backend/types/update_app_output.py +22 -0
- agenta/client/backend/types/uri.py +13 -28
- agenta/client/backend/types/validation_error.py +13 -28
- agenta/client/backend/types/variant_action.py +14 -29
- agenta/client/backend/types/variant_action_enum.py +1 -19
- agenta/client/backend/types/with_pagination.py +26 -0
- agenta/client/backend/types/workspace_member_response.py +23 -0
- agenta/client/backend/types/workspace_permission.py +25 -0
- agenta/client/backend/types/workspace_response.py +29 -0
- agenta/client/backend/types/workspace_role.py +15 -0
- agenta/client/backend/types/workspace_role_response.py +23 -0
- agenta/client/backend/variants/__init__.py +5 -0
- agenta/client/backend/variants/client.py +2814 -0
- agenta/client/backend/variants/types/__init__.py +7 -0
- agenta/client/backend/variants/types/add_variant_from_base_and_config_response.py +8 -0
- agenta/client/backend/vault/__init__.py +1 -0
- agenta/client/backend/vault/client.py +685 -0
- agenta/client/client.py +1 -1
- agenta/config.py +0 -2
- agenta/config.toml +0 -1
- agenta/docker/docker-assets/Dockerfile.cloud.template +2 -1
- agenta/docker/docker-assets/Dockerfile.template +2 -1
- agenta/docker/docker_utils.py +11 -12
- agenta/sdk/__init__.py +58 -7
- agenta/sdk/agenta_init.py +182 -164
- agenta/sdk/assets.py +95 -0
- agenta/sdk/client.py +56 -0
- agenta/sdk/context/__init__.py +0 -0
- agenta/sdk/context/exporting.py +25 -0
- agenta/sdk/context/routing.py +27 -0
- agenta/sdk/context/tracing.py +28 -0
- agenta/sdk/decorators/__init__.py +0 -0
- agenta/sdk/decorators/routing.py +576 -0
- agenta/sdk/decorators/tracing.py +296 -0
- agenta/sdk/litellm/__init__.py +1 -0
- agenta/sdk/litellm/litellm.py +314 -0
- agenta/sdk/litellm/mockllm.py +27 -0
- agenta/sdk/litellm/mocks/__init__.py +26 -0
- agenta/sdk/managers/__init__.py +6 -0
- agenta/sdk/managers/config.py +208 -0
- agenta/sdk/managers/deployment.py +45 -0
- agenta/sdk/managers/secrets.py +38 -0
- agenta/sdk/managers/shared.py +639 -0
- agenta/sdk/managers/variant.py +182 -0
- agenta/sdk/managers/vault.py +16 -0
- agenta/sdk/middleware/__init__.py +0 -0
- agenta/sdk/middleware/auth.py +180 -0
- agenta/sdk/middleware/cache.py +47 -0
- agenta/sdk/middleware/config.py +255 -0
- agenta/sdk/middleware/cors.py +29 -0
- agenta/sdk/middleware/inline.py +38 -0
- agenta/sdk/middleware/mock.py +33 -0
- agenta/sdk/middleware/otel.py +40 -0
- agenta/sdk/middleware/vault.py +145 -0
- agenta/sdk/router.py +0 -7
- agenta/sdk/tracing/__init__.py +1 -0
- agenta/sdk/tracing/attributes.py +141 -0
- agenta/sdk/tracing/conventions.py +49 -0
- agenta/sdk/tracing/exporters.py +103 -0
- agenta/sdk/tracing/inline.py +1146 -0
- agenta/sdk/tracing/processors.py +121 -0
- agenta/sdk/tracing/spans.py +136 -0
- agenta/sdk/tracing/tracing.py +237 -0
- agenta/sdk/types.py +478 -74
- agenta/sdk/utils/__init__.py +0 -0
- agenta/sdk/utils/constants.py +1 -0
- agenta/sdk/utils/{helper/openai_cost.py → costs.py} +3 -0
- agenta/sdk/utils/exceptions.py +59 -0
- agenta/sdk/utils/globals.py +6 -10
- agenta/sdk/utils/helpers.py +8 -0
- agenta/sdk/utils/logging.py +21 -0
- agenta/sdk/utils/singleton.py +13 -0
- agenta/sdk/utils/timing.py +58 -0
- {agenta-0.12.3.dist-info → agenta-0.32.0a1.dist-info}/METADATA +98 -151
- agenta-0.32.0a1.dist-info/RECORD +263 -0
- {agenta-0.12.3.dist-info → agenta-0.32.0a1.dist-info}/WHEEL +1 -1
- agenta/client/backend/types/add_variant_from_base_and_config_response.py +0 -7
- agenta/client/backend/types/app_variant_output.py +0 -47
- agenta/client/backend/types/app_variant_output_extended.py +0 -50
- agenta/client/backend/types/delete_evaluation.py +0 -36
- agenta/client/backend/types/evaluation_webhook.py +0 -36
- agenta/client/backend/types/feedback.py +0 -40
- agenta/client/backend/types/get_config_reponse.py +0 -39
- agenta/client/backend/types/list_api_keys_output.py +0 -39
- agenta/client/backend/types/trace.py +0 -48
- agenta/sdk/agenta_decorator.py +0 -443
- agenta/sdk/context.py +0 -41
- agenta-0.12.3.dist-info/RECORD +0 -114
- {agenta-0.12.3.dist-info → agenta-0.32.0a1.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,575 @@
|
|
|
1
|
+
# This file was auto-generated by Fern from our API Definition.
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import email.utils
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
import time
|
|
8
|
+
import typing
|
|
9
|
+
import urllib.parse
|
|
10
|
+
from contextlib import asynccontextmanager, contextmanager
|
|
11
|
+
from random import random
|
|
12
|
+
|
|
13
|
+
import httpx
|
|
14
|
+
|
|
15
|
+
from .file import File, convert_file_dict_to_httpx_tuples
|
|
16
|
+
from .jsonable_encoder import jsonable_encoder
|
|
17
|
+
from .query_encoder import encode_query
|
|
18
|
+
from .remove_none_from_dict import remove_none_from_dict
|
|
19
|
+
from .request_options import RequestOptions
|
|
20
|
+
|
|
21
|
+
INITIAL_RETRY_DELAY_SECONDS = 0.5
|
|
22
|
+
MAX_RETRY_DELAY_SECONDS = 10
|
|
23
|
+
MAX_RETRY_DELAY_SECONDS_FROM_HEADER = 30
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _parse_retry_after(response_headers: httpx.Headers) -> typing.Optional[float]:
|
|
27
|
+
"""
|
|
28
|
+
This function parses the `Retry-After` header in a HTTP response and returns the number of seconds to wait.
|
|
29
|
+
|
|
30
|
+
Inspired by the urllib3 retry implementation.
|
|
31
|
+
"""
|
|
32
|
+
retry_after_ms = response_headers.get("retry-after-ms")
|
|
33
|
+
if retry_after_ms is not None:
|
|
34
|
+
try:
|
|
35
|
+
return int(retry_after_ms) / 1000 if retry_after_ms > 0 else 0
|
|
36
|
+
except Exception:
|
|
37
|
+
pass
|
|
38
|
+
|
|
39
|
+
retry_after = response_headers.get("retry-after")
|
|
40
|
+
if retry_after is None:
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
# Attempt to parse the header as an int.
|
|
44
|
+
if re.match(r"^\s*[0-9]+\s*$", retry_after):
|
|
45
|
+
seconds = float(retry_after)
|
|
46
|
+
# Fallback to parsing it as a date.
|
|
47
|
+
else:
|
|
48
|
+
retry_date_tuple = email.utils.parsedate_tz(retry_after)
|
|
49
|
+
if retry_date_tuple is None:
|
|
50
|
+
return None
|
|
51
|
+
if retry_date_tuple[9] is None: # Python 2
|
|
52
|
+
# Assume UTC if no timezone was specified
|
|
53
|
+
# On Python2.7, parsedate_tz returns None for a timezone offset
|
|
54
|
+
# instead of 0 if no timezone is given, where mktime_tz treats
|
|
55
|
+
# a None timezone offset as local time.
|
|
56
|
+
retry_date_tuple = retry_date_tuple[:9] + (0,) + retry_date_tuple[10:]
|
|
57
|
+
|
|
58
|
+
retry_date = email.utils.mktime_tz(retry_date_tuple)
|
|
59
|
+
seconds = retry_date - time.time()
|
|
60
|
+
|
|
61
|
+
if seconds < 0:
|
|
62
|
+
seconds = 0
|
|
63
|
+
|
|
64
|
+
return seconds
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _retry_timeout(response: httpx.Response, retries: int) -> float:
|
|
68
|
+
"""
|
|
69
|
+
Determine the amount of time to wait before retrying a request.
|
|
70
|
+
This function begins by trying to parse a retry-after header from the response, and then proceeds to use exponential backoff
|
|
71
|
+
with a jitter to determine the number of seconds to wait.
|
|
72
|
+
"""
|
|
73
|
+
|
|
74
|
+
# If the API asks us to wait a certain amount of time (and it's a reasonable amount), just do what it says.
|
|
75
|
+
retry_after = _parse_retry_after(response.headers)
|
|
76
|
+
if retry_after is not None and retry_after <= MAX_RETRY_DELAY_SECONDS_FROM_HEADER:
|
|
77
|
+
return retry_after
|
|
78
|
+
|
|
79
|
+
# Apply exponential backoff, capped at MAX_RETRY_DELAY_SECONDS.
|
|
80
|
+
retry_delay = min(
|
|
81
|
+
INITIAL_RETRY_DELAY_SECONDS * pow(2.0, retries), MAX_RETRY_DELAY_SECONDS
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
# Add a randomness / jitter to the retry delay to avoid overwhelming the server with retries.
|
|
85
|
+
timeout = retry_delay * (1 - 0.25 * random())
|
|
86
|
+
return timeout if timeout >= 0 else 0
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _should_retry(response: httpx.Response) -> bool:
|
|
90
|
+
retriable_400s = [429, 408, 409]
|
|
91
|
+
return response.status_code >= 500 or response.status_code in retriable_400s
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def remove_omit_from_dict(
|
|
95
|
+
original: typing.Dict[str, typing.Optional[typing.Any]],
|
|
96
|
+
omit: typing.Optional[typing.Any],
|
|
97
|
+
) -> typing.Dict[str, typing.Any]:
|
|
98
|
+
if omit is None:
|
|
99
|
+
return original
|
|
100
|
+
new: typing.Dict[str, typing.Any] = {}
|
|
101
|
+
for key, value in original.items():
|
|
102
|
+
if value is not omit:
|
|
103
|
+
new[key] = value
|
|
104
|
+
return new
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def maybe_filter_request_body(
|
|
108
|
+
data: typing.Optional[typing.Any],
|
|
109
|
+
request_options: typing.Optional[RequestOptions],
|
|
110
|
+
omit: typing.Optional[typing.Any],
|
|
111
|
+
) -> typing.Optional[typing.Any]:
|
|
112
|
+
if data is None:
|
|
113
|
+
return (
|
|
114
|
+
jsonable_encoder(request_options.get("additional_body_parameters", {}))
|
|
115
|
+
or {}
|
|
116
|
+
if request_options is not None
|
|
117
|
+
else None
|
|
118
|
+
)
|
|
119
|
+
elif not isinstance(data, typing.Mapping):
|
|
120
|
+
data_content = jsonable_encoder(data)
|
|
121
|
+
else:
|
|
122
|
+
data_content = {
|
|
123
|
+
**(jsonable_encoder(remove_omit_from_dict(data, omit))), # type: ignore
|
|
124
|
+
**(
|
|
125
|
+
jsonable_encoder(request_options.get("additional_body_parameters", {}))
|
|
126
|
+
or {}
|
|
127
|
+
if request_options is not None
|
|
128
|
+
else {}
|
|
129
|
+
),
|
|
130
|
+
}
|
|
131
|
+
return data_content
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
# Abstracted out for testing purposes
|
|
135
|
+
def get_request_body(
|
|
136
|
+
*,
|
|
137
|
+
json: typing.Optional[typing.Any],
|
|
138
|
+
data: typing.Optional[typing.Any],
|
|
139
|
+
request_options: typing.Optional[RequestOptions],
|
|
140
|
+
omit: typing.Optional[typing.Any],
|
|
141
|
+
) -> typing.Tuple[typing.Optional[typing.Any], typing.Optional[typing.Any]]:
|
|
142
|
+
json_body = None
|
|
143
|
+
data_body = None
|
|
144
|
+
if data is not None:
|
|
145
|
+
data_body = maybe_filter_request_body(data, request_options, omit)
|
|
146
|
+
else:
|
|
147
|
+
# If both data and json are None, we send json data in the event extra properties are specified
|
|
148
|
+
json_body = maybe_filter_request_body(json, request_options, omit)
|
|
149
|
+
|
|
150
|
+
# If you have an empty JSON body, you should just send None
|
|
151
|
+
return (json_body if json_body != {} else None), (
|
|
152
|
+
data_body if data_body != {} else None
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class HttpClient:
|
|
157
|
+
def __init__(
|
|
158
|
+
self,
|
|
159
|
+
*,
|
|
160
|
+
httpx_client: httpx.Client,
|
|
161
|
+
base_timeout: typing.Callable[[], typing.Optional[float]],
|
|
162
|
+
base_headers: typing.Callable[[], typing.Dict[str, str]],
|
|
163
|
+
base_url: typing.Optional[typing.Callable[[], str]] = None,
|
|
164
|
+
):
|
|
165
|
+
self.base_url = base_url
|
|
166
|
+
self.base_timeout = base_timeout
|
|
167
|
+
self.base_headers = base_headers
|
|
168
|
+
self.httpx_client = httpx_client
|
|
169
|
+
|
|
170
|
+
def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str:
|
|
171
|
+
base_url = maybe_base_url
|
|
172
|
+
if self.base_url is not None and base_url is None:
|
|
173
|
+
base_url = self.base_url()
|
|
174
|
+
|
|
175
|
+
if base_url is None:
|
|
176
|
+
raise ValueError(
|
|
177
|
+
"A base_url is required to make this request, please provide one and try again."
|
|
178
|
+
)
|
|
179
|
+
return base_url
|
|
180
|
+
|
|
181
|
+
def request(
|
|
182
|
+
self,
|
|
183
|
+
path: typing.Optional[str] = None,
|
|
184
|
+
*,
|
|
185
|
+
method: str,
|
|
186
|
+
base_url: typing.Optional[str] = None,
|
|
187
|
+
params: typing.Optional[typing.Dict[str, typing.Any]] = None,
|
|
188
|
+
json: typing.Optional[typing.Any] = None,
|
|
189
|
+
data: typing.Optional[typing.Any] = None,
|
|
190
|
+
content: typing.Optional[
|
|
191
|
+
typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]
|
|
192
|
+
] = None,
|
|
193
|
+
files: typing.Optional[
|
|
194
|
+
typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]
|
|
195
|
+
] = None,
|
|
196
|
+
headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
|
|
197
|
+
request_options: typing.Optional[RequestOptions] = None,
|
|
198
|
+
retries: int = 0,
|
|
199
|
+
omit: typing.Optional[typing.Any] = None,
|
|
200
|
+
) -> httpx.Response:
|
|
201
|
+
base_url = self.get_base_url(base_url)
|
|
202
|
+
timeout = (
|
|
203
|
+
request_options.get("timeout_in_seconds")
|
|
204
|
+
if request_options is not None
|
|
205
|
+
and request_options.get("timeout_in_seconds") is not None
|
|
206
|
+
else self.base_timeout()
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
json_body, data_body = get_request_body(
|
|
210
|
+
json=json, data=data, request_options=request_options, omit=omit
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
response = self.httpx_client.request(
|
|
214
|
+
method=method,
|
|
215
|
+
url=urllib.parse.urljoin(f"{base_url}/", path),
|
|
216
|
+
headers=jsonable_encoder(
|
|
217
|
+
remove_none_from_dict(
|
|
218
|
+
{
|
|
219
|
+
**self.base_headers(),
|
|
220
|
+
**(headers if headers is not None else {}),
|
|
221
|
+
**(
|
|
222
|
+
request_options.get("additional_headers", {}) or {}
|
|
223
|
+
if request_options is not None
|
|
224
|
+
else {}
|
|
225
|
+
),
|
|
226
|
+
}
|
|
227
|
+
)
|
|
228
|
+
),
|
|
229
|
+
params=encode_query(
|
|
230
|
+
jsonable_encoder(
|
|
231
|
+
remove_none_from_dict(
|
|
232
|
+
remove_omit_from_dict(
|
|
233
|
+
{
|
|
234
|
+
**(params if params is not None else {}),
|
|
235
|
+
**(
|
|
236
|
+
request_options.get(
|
|
237
|
+
"additional_query_parameters", {}
|
|
238
|
+
)
|
|
239
|
+
or {}
|
|
240
|
+
if request_options is not None
|
|
241
|
+
else {}
|
|
242
|
+
),
|
|
243
|
+
},
|
|
244
|
+
omit,
|
|
245
|
+
)
|
|
246
|
+
)
|
|
247
|
+
)
|
|
248
|
+
),
|
|
249
|
+
json=json_body,
|
|
250
|
+
data=data_body,
|
|
251
|
+
content=content,
|
|
252
|
+
files=(
|
|
253
|
+
convert_file_dict_to_httpx_tuples(
|
|
254
|
+
remove_omit_from_dict(remove_none_from_dict(files), omit)
|
|
255
|
+
)
|
|
256
|
+
if (files is not None and files is not omit)
|
|
257
|
+
else None
|
|
258
|
+
),
|
|
259
|
+
timeout=timeout,
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
max_retries: int = (
|
|
263
|
+
request_options.get("max_retries", 0) if request_options is not None else 0
|
|
264
|
+
)
|
|
265
|
+
if _should_retry(response=response):
|
|
266
|
+
if max_retries > retries:
|
|
267
|
+
time.sleep(_retry_timeout(response=response, retries=retries))
|
|
268
|
+
return self.request(
|
|
269
|
+
path=path,
|
|
270
|
+
method=method,
|
|
271
|
+
base_url=base_url,
|
|
272
|
+
params=params,
|
|
273
|
+
json=json,
|
|
274
|
+
content=content,
|
|
275
|
+
files=files,
|
|
276
|
+
headers=headers,
|
|
277
|
+
request_options=request_options,
|
|
278
|
+
retries=retries + 1,
|
|
279
|
+
omit=omit,
|
|
280
|
+
)
|
|
281
|
+
|
|
282
|
+
return response
|
|
283
|
+
|
|
284
|
+
@contextmanager
|
|
285
|
+
def stream(
|
|
286
|
+
self,
|
|
287
|
+
path: typing.Optional[str] = None,
|
|
288
|
+
*,
|
|
289
|
+
method: str,
|
|
290
|
+
base_url: typing.Optional[str] = None,
|
|
291
|
+
params: typing.Optional[typing.Dict[str, typing.Any]] = None,
|
|
292
|
+
json: typing.Optional[typing.Any] = None,
|
|
293
|
+
data: typing.Optional[typing.Any] = None,
|
|
294
|
+
content: typing.Optional[
|
|
295
|
+
typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]
|
|
296
|
+
] = None,
|
|
297
|
+
files: typing.Optional[
|
|
298
|
+
typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]
|
|
299
|
+
] = None,
|
|
300
|
+
headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
|
|
301
|
+
request_options: typing.Optional[RequestOptions] = None,
|
|
302
|
+
retries: int = 0,
|
|
303
|
+
omit: typing.Optional[typing.Any] = None,
|
|
304
|
+
) -> typing.Iterator[httpx.Response]:
|
|
305
|
+
base_url = self.get_base_url(base_url)
|
|
306
|
+
timeout = (
|
|
307
|
+
request_options.get("timeout_in_seconds")
|
|
308
|
+
if request_options is not None
|
|
309
|
+
and request_options.get("timeout_in_seconds") is not None
|
|
310
|
+
else self.base_timeout()
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
json_body, data_body = get_request_body(
|
|
314
|
+
json=json, data=data, request_options=request_options, omit=omit
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
with self.httpx_client.stream(
|
|
318
|
+
method=method,
|
|
319
|
+
url=urllib.parse.urljoin(f"{base_url}/", path),
|
|
320
|
+
headers=jsonable_encoder(
|
|
321
|
+
remove_none_from_dict(
|
|
322
|
+
{
|
|
323
|
+
**self.base_headers(),
|
|
324
|
+
**(headers if headers is not None else {}),
|
|
325
|
+
**(
|
|
326
|
+
request_options.get("additional_headers", {})
|
|
327
|
+
if request_options is not None
|
|
328
|
+
else {}
|
|
329
|
+
),
|
|
330
|
+
}
|
|
331
|
+
)
|
|
332
|
+
),
|
|
333
|
+
params=encode_query(
|
|
334
|
+
jsonable_encoder(
|
|
335
|
+
remove_none_from_dict(
|
|
336
|
+
remove_omit_from_dict(
|
|
337
|
+
{
|
|
338
|
+
**(params if params is not None else {}),
|
|
339
|
+
**(
|
|
340
|
+
request_options.get(
|
|
341
|
+
"additional_query_parameters", {}
|
|
342
|
+
)
|
|
343
|
+
if request_options is not None
|
|
344
|
+
else {}
|
|
345
|
+
),
|
|
346
|
+
},
|
|
347
|
+
omit,
|
|
348
|
+
)
|
|
349
|
+
)
|
|
350
|
+
)
|
|
351
|
+
),
|
|
352
|
+
json=json_body,
|
|
353
|
+
data=data_body,
|
|
354
|
+
content=content,
|
|
355
|
+
files=(
|
|
356
|
+
convert_file_dict_to_httpx_tuples(
|
|
357
|
+
remove_omit_from_dict(remove_none_from_dict(files), omit)
|
|
358
|
+
)
|
|
359
|
+
if (files is not None and files is not omit)
|
|
360
|
+
else None
|
|
361
|
+
),
|
|
362
|
+
timeout=timeout,
|
|
363
|
+
) as stream:
|
|
364
|
+
yield stream
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
class AsyncHttpClient:
|
|
368
|
+
def __init__(
|
|
369
|
+
self,
|
|
370
|
+
*,
|
|
371
|
+
httpx_client: httpx.AsyncClient,
|
|
372
|
+
base_timeout: typing.Callable[[], typing.Optional[float]],
|
|
373
|
+
base_headers: typing.Callable[[], typing.Dict[str, str]],
|
|
374
|
+
base_url: typing.Optional[typing.Callable[[], str]] = None,
|
|
375
|
+
):
|
|
376
|
+
self.base_url = base_url
|
|
377
|
+
self.base_timeout = base_timeout
|
|
378
|
+
self.base_headers = base_headers
|
|
379
|
+
self.httpx_client = httpx_client
|
|
380
|
+
|
|
381
|
+
def get_base_url(self, maybe_base_url: typing.Optional[str]) -> str:
|
|
382
|
+
base_url = maybe_base_url
|
|
383
|
+
if self.base_url is not None and base_url is None:
|
|
384
|
+
base_url = self.base_url()
|
|
385
|
+
|
|
386
|
+
if base_url is None:
|
|
387
|
+
raise ValueError(
|
|
388
|
+
"A base_url is required to make this request, please provide one and try again."
|
|
389
|
+
)
|
|
390
|
+
return base_url
|
|
391
|
+
|
|
392
|
+
async def request(
|
|
393
|
+
self,
|
|
394
|
+
path: typing.Optional[str] = None,
|
|
395
|
+
*,
|
|
396
|
+
method: str,
|
|
397
|
+
base_url: typing.Optional[str] = None,
|
|
398
|
+
params: typing.Optional[typing.Dict[str, typing.Any]] = None,
|
|
399
|
+
json: typing.Optional[typing.Any] = None,
|
|
400
|
+
data: typing.Optional[typing.Any] = None,
|
|
401
|
+
content: typing.Optional[
|
|
402
|
+
typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]
|
|
403
|
+
] = None,
|
|
404
|
+
files: typing.Optional[
|
|
405
|
+
typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]
|
|
406
|
+
] = None,
|
|
407
|
+
headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
|
|
408
|
+
request_options: typing.Optional[RequestOptions] = None,
|
|
409
|
+
retries: int = 0,
|
|
410
|
+
omit: typing.Optional[typing.Any] = None,
|
|
411
|
+
) -> httpx.Response:
|
|
412
|
+
base_url = self.get_base_url(base_url)
|
|
413
|
+
timeout = (
|
|
414
|
+
request_options.get("timeout_in_seconds")
|
|
415
|
+
if request_options is not None
|
|
416
|
+
and request_options.get("timeout_in_seconds") is not None
|
|
417
|
+
else self.base_timeout()
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
json_body, data_body = get_request_body(
|
|
421
|
+
json=json, data=data, request_options=request_options, omit=omit
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
# Add the input to each of these and do None-safety checks
|
|
425
|
+
response = await self.httpx_client.request(
|
|
426
|
+
method=method,
|
|
427
|
+
url=urllib.parse.urljoin(f"{base_url}/", path),
|
|
428
|
+
headers=jsonable_encoder(
|
|
429
|
+
remove_none_from_dict(
|
|
430
|
+
{
|
|
431
|
+
**self.base_headers(),
|
|
432
|
+
**(headers if headers is not None else {}),
|
|
433
|
+
**(
|
|
434
|
+
request_options.get("additional_headers", {}) or {}
|
|
435
|
+
if request_options is not None
|
|
436
|
+
else {}
|
|
437
|
+
),
|
|
438
|
+
}
|
|
439
|
+
)
|
|
440
|
+
),
|
|
441
|
+
params=encode_query(
|
|
442
|
+
jsonable_encoder(
|
|
443
|
+
remove_none_from_dict(
|
|
444
|
+
remove_omit_from_dict(
|
|
445
|
+
{
|
|
446
|
+
**(params if params is not None else {}),
|
|
447
|
+
**(
|
|
448
|
+
request_options.get(
|
|
449
|
+
"additional_query_parameters", {}
|
|
450
|
+
)
|
|
451
|
+
or {}
|
|
452
|
+
if request_options is not None
|
|
453
|
+
else {}
|
|
454
|
+
),
|
|
455
|
+
},
|
|
456
|
+
omit,
|
|
457
|
+
)
|
|
458
|
+
)
|
|
459
|
+
)
|
|
460
|
+
),
|
|
461
|
+
json=json_body,
|
|
462
|
+
data=data_body,
|
|
463
|
+
content=content,
|
|
464
|
+
files=(
|
|
465
|
+
convert_file_dict_to_httpx_tuples(
|
|
466
|
+
remove_omit_from_dict(remove_none_from_dict(files), omit)
|
|
467
|
+
)
|
|
468
|
+
if files is not None
|
|
469
|
+
else None
|
|
470
|
+
),
|
|
471
|
+
timeout=timeout,
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
max_retries: int = (
|
|
475
|
+
request_options.get("max_retries", 0) if request_options is not None else 0
|
|
476
|
+
)
|
|
477
|
+
if _should_retry(response=response):
|
|
478
|
+
if max_retries > retries:
|
|
479
|
+
await asyncio.sleep(_retry_timeout(response=response, retries=retries))
|
|
480
|
+
return await self.request(
|
|
481
|
+
path=path,
|
|
482
|
+
method=method,
|
|
483
|
+
base_url=base_url,
|
|
484
|
+
params=params,
|
|
485
|
+
json=json,
|
|
486
|
+
content=content,
|
|
487
|
+
files=files,
|
|
488
|
+
headers=headers,
|
|
489
|
+
request_options=request_options,
|
|
490
|
+
retries=retries + 1,
|
|
491
|
+
omit=omit,
|
|
492
|
+
)
|
|
493
|
+
return response
|
|
494
|
+
|
|
495
|
+
@asynccontextmanager
|
|
496
|
+
async def stream(
|
|
497
|
+
self,
|
|
498
|
+
path: typing.Optional[str] = None,
|
|
499
|
+
*,
|
|
500
|
+
method: str,
|
|
501
|
+
base_url: typing.Optional[str] = None,
|
|
502
|
+
params: typing.Optional[typing.Dict[str, typing.Any]] = None,
|
|
503
|
+
json: typing.Optional[typing.Any] = None,
|
|
504
|
+
data: typing.Optional[typing.Any] = None,
|
|
505
|
+
content: typing.Optional[
|
|
506
|
+
typing.Union[bytes, typing.Iterator[bytes], typing.AsyncIterator[bytes]]
|
|
507
|
+
] = None,
|
|
508
|
+
files: typing.Optional[
|
|
509
|
+
typing.Dict[str, typing.Optional[typing.Union[File, typing.List[File]]]]
|
|
510
|
+
] = None,
|
|
511
|
+
headers: typing.Optional[typing.Dict[str, typing.Any]] = None,
|
|
512
|
+
request_options: typing.Optional[RequestOptions] = None,
|
|
513
|
+
retries: int = 0,
|
|
514
|
+
omit: typing.Optional[typing.Any] = None,
|
|
515
|
+
) -> typing.AsyncIterator[httpx.Response]:
|
|
516
|
+
base_url = self.get_base_url(base_url)
|
|
517
|
+
timeout = (
|
|
518
|
+
request_options.get("timeout_in_seconds")
|
|
519
|
+
if request_options is not None
|
|
520
|
+
and request_options.get("timeout_in_seconds") is not None
|
|
521
|
+
else self.base_timeout()
|
|
522
|
+
)
|
|
523
|
+
|
|
524
|
+
json_body, data_body = get_request_body(
|
|
525
|
+
json=json, data=data, request_options=request_options, omit=omit
|
|
526
|
+
)
|
|
527
|
+
|
|
528
|
+
async with self.httpx_client.stream(
|
|
529
|
+
method=method,
|
|
530
|
+
url=urllib.parse.urljoin(f"{base_url}/", path),
|
|
531
|
+
headers=jsonable_encoder(
|
|
532
|
+
remove_none_from_dict(
|
|
533
|
+
{
|
|
534
|
+
**self.base_headers(),
|
|
535
|
+
**(headers if headers is not None else {}),
|
|
536
|
+
**(
|
|
537
|
+
request_options.get("additional_headers", {})
|
|
538
|
+
if request_options is not None
|
|
539
|
+
else {}
|
|
540
|
+
),
|
|
541
|
+
}
|
|
542
|
+
)
|
|
543
|
+
),
|
|
544
|
+
params=encode_query(
|
|
545
|
+
jsonable_encoder(
|
|
546
|
+
remove_none_from_dict(
|
|
547
|
+
remove_omit_from_dict(
|
|
548
|
+
{
|
|
549
|
+
**(params if params is not None else {}),
|
|
550
|
+
**(
|
|
551
|
+
request_options.get(
|
|
552
|
+
"additional_query_parameters", {}
|
|
553
|
+
)
|
|
554
|
+
if request_options is not None
|
|
555
|
+
else {}
|
|
556
|
+
),
|
|
557
|
+
},
|
|
558
|
+
omit=omit,
|
|
559
|
+
)
|
|
560
|
+
)
|
|
561
|
+
)
|
|
562
|
+
),
|
|
563
|
+
json=json_body,
|
|
564
|
+
data=data_body,
|
|
565
|
+
content=content,
|
|
566
|
+
files=(
|
|
567
|
+
convert_file_dict_to_httpx_tuples(
|
|
568
|
+
remove_omit_from_dict(remove_none_from_dict(files), omit)
|
|
569
|
+
)
|
|
570
|
+
if files is not None
|
|
571
|
+
else None
|
|
572
|
+
),
|
|
573
|
+
timeout=timeout,
|
|
574
|
+
) as stream:
|
|
575
|
+
yield stream
|
|
@@ -8,41 +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
18
|
|
|
19
|
-
|
|
20
|
-
import pydantic.v1 as pydantic # type: ignore
|
|
21
|
-
except ImportError:
|
|
22
|
-
import pydantic # type: ignore
|
|
19
|
+
import pydantic
|
|
23
20
|
|
|
24
21
|
from .datetime_utils import serialize_datetime
|
|
22
|
+
from .pydantic_utilities import (
|
|
23
|
+
IS_PYDANTIC_V2,
|
|
24
|
+
encode_by_type,
|
|
25
|
+
to_jsonable_with_fallback,
|
|
26
|
+
)
|
|
25
27
|
|
|
26
28
|
SetIntStr = Set[Union[int, str]]
|
|
27
29
|
DictIntStrAny = Dict[Union[int, str], Any]
|
|
28
30
|
|
|
29
31
|
|
|
30
|
-
def generate_encoders_by_class_tuples(
|
|
31
|
-
type_encoder_map: Dict[Any, Callable[[Any], Any]]
|
|
32
|
-
) -> Dict[Callable[[Any], Any], Tuple[Any, ...]]:
|
|
33
|
-
encoders_by_class_tuples: Dict[Callable[[Any], Any], Tuple[Any, ...]] = defaultdict(
|
|
34
|
-
tuple
|
|
35
|
-
)
|
|
36
|
-
for type_, encoder in type_encoder_map.items():
|
|
37
|
-
encoders_by_class_tuples[encoder] += (type_,)
|
|
38
|
-
return encoders_by_class_tuples
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
encoders_by_class_tuples = generate_encoders_by_class_tuples(
|
|
42
|
-
pydantic.json.ENCODERS_BY_TYPE
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
|
|
46
32
|
def jsonable_encoder(
|
|
47
33
|
obj: Any, custom_encoder: Optional[Dict[Any, Callable[[Any], Any]]] = None
|
|
48
34
|
) -> Any:
|
|
@@ -55,26 +41,33 @@ def jsonable_encoder(
|
|
|
55
41
|
if isinstance(obj, encoder_type):
|
|
56
42
|
return encoder_instance(obj)
|
|
57
43
|
if isinstance(obj, pydantic.BaseModel):
|
|
58
|
-
|
|
44
|
+
if IS_PYDANTIC_V2:
|
|
45
|
+
encoder = getattr(obj.model_config, "json_encoders", {}) # type: ignore # Pydantic v2
|
|
46
|
+
else:
|
|
47
|
+
encoder = getattr(obj.__config__, "json_encoders", {}) # type: ignore # Pydantic v1
|
|
59
48
|
if custom_encoder:
|
|
60
49
|
encoder.update(custom_encoder)
|
|
61
50
|
obj_dict = obj.dict(by_alias=True)
|
|
62
51
|
if "__root__" in obj_dict:
|
|
63
52
|
obj_dict = obj_dict["__root__"]
|
|
53
|
+
if "root" in obj_dict:
|
|
54
|
+
obj_dict = obj_dict["root"]
|
|
64
55
|
return jsonable_encoder(obj_dict, custom_encoder=encoder)
|
|
65
56
|
if dataclasses.is_dataclass(obj):
|
|
66
|
-
obj_dict = dataclasses.asdict(obj)
|
|
57
|
+
obj_dict = dataclasses.asdict(obj) # type: ignore
|
|
67
58
|
return jsonable_encoder(obj_dict, custom_encoder=custom_encoder)
|
|
59
|
+
if isinstance(obj, bytes):
|
|
60
|
+
return base64.b64encode(obj).decode("utf-8")
|
|
68
61
|
if isinstance(obj, Enum):
|
|
69
62
|
return obj.value
|
|
70
63
|
if isinstance(obj, PurePath):
|
|
71
64
|
return str(obj)
|
|
72
65
|
if isinstance(obj, (str, int, float, type(None))):
|
|
73
66
|
return obj
|
|
74
|
-
if isinstance(obj, dt.date):
|
|
75
|
-
return str(obj)
|
|
76
67
|
if isinstance(obj, dt.datetime):
|
|
77
68
|
return serialize_datetime(obj)
|
|
69
|
+
if isinstance(obj, dt.date):
|
|
70
|
+
return str(obj)
|
|
78
71
|
if isinstance(obj, dict):
|
|
79
72
|
encoded_dict = {}
|
|
80
73
|
allowed_keys = set(obj.keys())
|
|
@@ -90,20 +83,21 @@ def jsonable_encoder(
|
|
|
90
83
|
encoded_list.append(jsonable_encoder(item, custom_encoder=custom_encoder))
|
|
91
84
|
return encoded_list
|
|
92
85
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
return encoder(obj)
|
|
86
|
+
def fallback_serializer(o: Any) -> Any:
|
|
87
|
+
attempt_encode = encode_by_type(o)
|
|
88
|
+
if attempt_encode is not None:
|
|
89
|
+
return attempt_encode
|
|
98
90
|
|
|
99
|
-
try:
|
|
100
|
-
data = dict(obj)
|
|
101
|
-
except Exception as e:
|
|
102
|
-
errors: List[Exception] = []
|
|
103
|
-
errors.append(e)
|
|
104
91
|
try:
|
|
105
|
-
data =
|
|
92
|
+
data = dict(o)
|
|
106
93
|
except Exception as e:
|
|
94
|
+
errors: List[Exception] = []
|
|
107
95
|
errors.append(e)
|
|
108
|
-
|
|
109
|
-
|
|
96
|
+
try:
|
|
97
|
+
data = vars(o)
|
|
98
|
+
except Exception as e:
|
|
99
|
+
errors.append(e)
|
|
100
|
+
raise ValueError(errors) from e
|
|
101
|
+
return jsonable_encoder(data, custom_encoder=custom_encoder)
|
|
102
|
+
|
|
103
|
+
return to_jsonable_with_fallback(obj, fallback_serializer)
|