train-travel 0.0.7__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 (62) hide show
  1. train_travel/__init__.py +17 -0
  2. train_travel/_hooks/__init__.py +6 -0
  3. train_travel/_hooks/oauth2scopes.py +16 -0
  4. train_travel/_hooks/registration.py +13 -0
  5. train_travel/_hooks/sdkhooks.py +76 -0
  6. train_travel/_hooks/types.py +112 -0
  7. train_travel/_version.py +15 -0
  8. train_travel/basesdk.py +388 -0
  9. train_travel/bookings.py +1190 -0
  10. train_travel/errors/__init__.py +63 -0
  11. train_travel/errors/no_response_error.py +17 -0
  12. train_travel/errors/responsevalidationerror.py +27 -0
  13. train_travel/errors/traintraveldefaulterror.py +40 -0
  14. train_travel/errors/traintravelerror.py +30 -0
  15. train_travel/httpclient.py +125 -0
  16. train_travel/models/__init__.py +416 -0
  17. train_travel/models/booking.py +59 -0
  18. train_travel/models/booking_input.py +52 -0
  19. train_travel/models/bookingpayment.py +221 -0
  20. train_travel/models/create_booking_paymentop.py +269 -0
  21. train_travel/models/create_booking_rawop.py +144 -0
  22. train_travel/models/create_bookingop.py +144 -0
  23. train_travel/models/delete_bookingop.py +30 -0
  24. train_travel/models/get_bookingop.py +159 -0
  25. train_travel/models/get_bookingsop.py +198 -0
  26. train_travel/models/get_stationsop.py +230 -0
  27. train_travel/models/get_tripsop.py +324 -0
  28. train_travel/models/links_booking.py +35 -0
  29. train_travel/models/links_self.py +36 -0
  30. train_travel/models/new_bookingop.py +92 -0
  31. train_travel/models/security.py +39 -0
  32. train_travel/models/station.py +57 -0
  33. train_travel/models/trip.py +90 -0
  34. train_travel/payments.py +262 -0
  35. train_travel/py.typed +1 -0
  36. train_travel/sdk.py +213 -0
  37. train_travel/sdkconfiguration.py +51 -0
  38. train_travel/stations.py +284 -0
  39. train_travel/trips.py +291 -0
  40. train_travel/types/__init__.py +21 -0
  41. train_travel/types/basemodel.py +77 -0
  42. train_travel/utils/__init__.py +206 -0
  43. train_travel/utils/annotations.py +79 -0
  44. train_travel/utils/datetimes.py +23 -0
  45. train_travel/utils/enums.py +134 -0
  46. train_travel/utils/eventstreaming.py +248 -0
  47. train_travel/utils/forms.py +234 -0
  48. train_travel/utils/headers.py +136 -0
  49. train_travel/utils/logger.py +27 -0
  50. train_travel/utils/metadata.py +118 -0
  51. train_travel/utils/queryparams.py +217 -0
  52. train_travel/utils/requestbodies.py +66 -0
  53. train_travel/utils/retries.py +281 -0
  54. train_travel/utils/security.py +192 -0
  55. train_travel/utils/serializers.py +229 -0
  56. train_travel/utils/unmarshal_json_response.py +38 -0
  57. train_travel/utils/url.py +155 -0
  58. train_travel/utils/values.py +137 -0
  59. train_travel-0.0.7.dist-info/METADATA +567 -0
  60. train_travel-0.0.7.dist-info/RECORD +62 -0
  61. train_travel-0.0.7.dist-info/WHEEL +5 -0
  62. train_travel-0.0.7.dist-info/top_level.txt +1 -0
train_travel/trips.py ADDED
@@ -0,0 +1,291 @@
1
+ """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
2
+
3
+ from .basesdk import BaseSDK
4
+ from datetime import datetime
5
+ from enum import Enum
6
+ from train_travel import errors, models, utils
7
+ from train_travel._hooks import HookContext
8
+ from train_travel.types import OptionalNullable, UNSET
9
+ from train_travel.utils import get_security_from_env
10
+ from train_travel.utils.unmarshal_json_response import unmarshal_json_response
11
+ from typing import Mapping, Optional
12
+
13
+
14
+ class GetTripsAcceptEnum(str, Enum):
15
+ APPLICATION_JSON = "application/json"
16
+ APPLICATION_XML = "application/xml"
17
+
18
+
19
+ class Trips(BaseSDK):
20
+ r"""Timetables and routes for train trips between stations, including pricing
21
+ and availability.
22
+
23
+ """
24
+
25
+ def get_trips(
26
+ self,
27
+ *,
28
+ origin: str,
29
+ destination: str,
30
+ date_: datetime,
31
+ page: Optional[int] = 1,
32
+ limit: Optional[int] = 10,
33
+ bicycles: Optional[bool] = False,
34
+ dogs: Optional[bool] = False,
35
+ retries: OptionalNullable[utils.RetryConfig] = UNSET,
36
+ server_url: Optional[str] = None,
37
+ timeout_ms: Optional[int] = None,
38
+ accept_header_override: Optional[GetTripsAcceptEnum] = None,
39
+ http_headers: Optional[Mapping[str, str]] = None,
40
+ ) -> models.GetTripsResponse:
41
+ r"""Get available train trips
42
+
43
+ Returns a list of available train trips between the specified origin and destination stations on the given date, and allows for filtering by bicycle and dog allowances.
44
+
45
+
46
+ :param origin: The ID of the origin station
47
+ :param destination: The ID of the destination station
48
+ :param date_: The date and time of the trip in ISO 8601 format in origin station's timezone.
49
+ :param page: The page number to return
50
+ :param limit: The number of items to return per page
51
+ :param bicycles: Only return trips where bicycles are known to be allowed
52
+ :param dogs: Only return trips where dogs are known to be allowed
53
+ :param retries: Override the default retry configuration for this method
54
+ :param server_url: Override the default server URL for this method
55
+ :param timeout_ms: Override the default request timeout configuration for this method in milliseconds
56
+ :param accept_header_override: Override the default accept header for this method
57
+ :param http_headers: Additional headers to set or replace on requests.
58
+ """
59
+ base_url = None
60
+ url_variables = None
61
+ if timeout_ms is None:
62
+ timeout_ms = self.sdk_configuration.timeout_ms
63
+
64
+ if server_url is not None:
65
+ base_url = server_url
66
+ else:
67
+ base_url = self._get_url(base_url, url_variables)
68
+
69
+ request = models.GetTripsRequest(
70
+ page=page,
71
+ limit=limit,
72
+ origin=origin,
73
+ destination=destination,
74
+ date_=date_,
75
+ bicycles=bicycles,
76
+ dogs=dogs,
77
+ )
78
+
79
+ req = self._build_request(
80
+ method="GET",
81
+ path="/trips",
82
+ base_url=base_url,
83
+ url_variables=url_variables,
84
+ request=request,
85
+ request_body_required=False,
86
+ request_has_path_params=False,
87
+ request_has_query_params=True,
88
+ user_agent_header="user-agent",
89
+ accept_header_value=accept_header_override.value
90
+ if accept_header_override is not None
91
+ else "application/json;q=1, application/xml;q=0",
92
+ http_headers=http_headers,
93
+ security=self.sdk_configuration.security,
94
+ allow_empty_value=None,
95
+ timeout_ms=timeout_ms,
96
+ )
97
+
98
+ if retries == UNSET:
99
+ if self.sdk_configuration.retry_config is not UNSET:
100
+ retries = self.sdk_configuration.retry_config
101
+ else:
102
+ retries = utils.RetryConfig(
103
+ "backoff", utils.BackoffStrategy(500, 60000, 1.5, 3600000), True
104
+ )
105
+
106
+ retry_config = None
107
+ if isinstance(retries, utils.RetryConfig):
108
+ retry_config = (retries, ["5XX"])
109
+
110
+ http_res = self.do_request(
111
+ hook_ctx=HookContext(
112
+ config=self.sdk_configuration,
113
+ base_url=base_url or "",
114
+ operation_id="get-trips",
115
+ oauth2_scopes=None,
116
+ security_source=get_security_from_env(
117
+ self.sdk_configuration.security, models.Security
118
+ ),
119
+ ),
120
+ request=req,
121
+ error_status_codes=["400", "401", "403", "429", "4XX", "500", "5XX"],
122
+ retry_config=retry_config,
123
+ )
124
+
125
+ if utils.match_response(http_res, "200", "application/json"):
126
+ return models.GetTripsResponse(
127
+ result=unmarshal_json_response(models.GetTripsResponseBody, http_res),
128
+ headers=utils.get_response_headers(http_res.headers),
129
+ )
130
+ if utils.match_response(http_res, "200", "application/xml"):
131
+ http_res_bytes = utils.stream_to_bytes(http_res)
132
+ return models.GetTripsResponse(
133
+ result=http_res_bytes,
134
+ headers=utils.get_response_headers(http_res.headers),
135
+ )
136
+ if utils.match_response(http_res, ["400", "401", "403", "429"], "*"):
137
+ http_res_text = utils.stream_to_text(http_res)
138
+ raise errors.TrainTravelDefaultError(
139
+ "API error occurred", http_res, http_res_text
140
+ )
141
+ if utils.match_response(http_res, "500", "*"):
142
+ http_res_text = utils.stream_to_text(http_res)
143
+ raise errors.TrainTravelDefaultError(
144
+ "API error occurred", http_res, http_res_text
145
+ )
146
+ if utils.match_response(http_res, "4XX", "*"):
147
+ http_res_text = utils.stream_to_text(http_res)
148
+ raise errors.TrainTravelDefaultError(
149
+ "API error occurred", http_res, http_res_text
150
+ )
151
+ if utils.match_response(http_res, "5XX", "*"):
152
+ http_res_text = utils.stream_to_text(http_res)
153
+ raise errors.TrainTravelDefaultError(
154
+ "API error occurred", http_res, http_res_text
155
+ )
156
+
157
+ raise errors.TrainTravelDefaultError("Unexpected response received", http_res)
158
+
159
+ async def get_trips_async(
160
+ self,
161
+ *,
162
+ origin: str,
163
+ destination: str,
164
+ date_: datetime,
165
+ page: Optional[int] = 1,
166
+ limit: Optional[int] = 10,
167
+ bicycles: Optional[bool] = False,
168
+ dogs: Optional[bool] = False,
169
+ retries: OptionalNullable[utils.RetryConfig] = UNSET,
170
+ server_url: Optional[str] = None,
171
+ timeout_ms: Optional[int] = None,
172
+ accept_header_override: Optional[GetTripsAcceptEnum] = None,
173
+ http_headers: Optional[Mapping[str, str]] = None,
174
+ ) -> models.GetTripsResponse:
175
+ r"""Get available train trips
176
+
177
+ Returns a list of available train trips between the specified origin and destination stations on the given date, and allows for filtering by bicycle and dog allowances.
178
+
179
+
180
+ :param origin: The ID of the origin station
181
+ :param destination: The ID of the destination station
182
+ :param date_: The date and time of the trip in ISO 8601 format in origin station's timezone.
183
+ :param page: The page number to return
184
+ :param limit: The number of items to return per page
185
+ :param bicycles: Only return trips where bicycles are known to be allowed
186
+ :param dogs: Only return trips where dogs are known to be allowed
187
+ :param retries: Override the default retry configuration for this method
188
+ :param server_url: Override the default server URL for this method
189
+ :param timeout_ms: Override the default request timeout configuration for this method in milliseconds
190
+ :param accept_header_override: Override the default accept header for this method
191
+ :param http_headers: Additional headers to set or replace on requests.
192
+ """
193
+ base_url = None
194
+ url_variables = None
195
+ if timeout_ms is None:
196
+ timeout_ms = self.sdk_configuration.timeout_ms
197
+
198
+ if server_url is not None:
199
+ base_url = server_url
200
+ else:
201
+ base_url = self._get_url(base_url, url_variables)
202
+
203
+ request = models.GetTripsRequest(
204
+ page=page,
205
+ limit=limit,
206
+ origin=origin,
207
+ destination=destination,
208
+ date_=date_,
209
+ bicycles=bicycles,
210
+ dogs=dogs,
211
+ )
212
+
213
+ req = self._build_request_async(
214
+ method="GET",
215
+ path="/trips",
216
+ base_url=base_url,
217
+ url_variables=url_variables,
218
+ request=request,
219
+ request_body_required=False,
220
+ request_has_path_params=False,
221
+ request_has_query_params=True,
222
+ user_agent_header="user-agent",
223
+ accept_header_value=accept_header_override.value
224
+ if accept_header_override is not None
225
+ else "application/json;q=1, application/xml;q=0",
226
+ http_headers=http_headers,
227
+ security=self.sdk_configuration.security,
228
+ allow_empty_value=None,
229
+ timeout_ms=timeout_ms,
230
+ )
231
+
232
+ if retries == UNSET:
233
+ if self.sdk_configuration.retry_config is not UNSET:
234
+ retries = self.sdk_configuration.retry_config
235
+ else:
236
+ retries = utils.RetryConfig(
237
+ "backoff", utils.BackoffStrategy(500, 60000, 1.5, 3600000), True
238
+ )
239
+
240
+ retry_config = None
241
+ if isinstance(retries, utils.RetryConfig):
242
+ retry_config = (retries, ["5XX"])
243
+
244
+ http_res = await self.do_request_async(
245
+ hook_ctx=HookContext(
246
+ config=self.sdk_configuration,
247
+ base_url=base_url or "",
248
+ operation_id="get-trips",
249
+ oauth2_scopes=None,
250
+ security_source=get_security_from_env(
251
+ self.sdk_configuration.security, models.Security
252
+ ),
253
+ ),
254
+ request=req,
255
+ error_status_codes=["400", "401", "403", "429", "4XX", "500", "5XX"],
256
+ retry_config=retry_config,
257
+ )
258
+
259
+ if utils.match_response(http_res, "200", "application/json"):
260
+ return models.GetTripsResponse(
261
+ result=unmarshal_json_response(models.GetTripsResponseBody, http_res),
262
+ headers=utils.get_response_headers(http_res.headers),
263
+ )
264
+ if utils.match_response(http_res, "200", "application/xml"):
265
+ http_res_bytes = await utils.stream_to_bytes_async(http_res)
266
+ return models.GetTripsResponse(
267
+ result=http_res_bytes,
268
+ headers=utils.get_response_headers(http_res.headers),
269
+ )
270
+ if utils.match_response(http_res, ["400", "401", "403", "429"], "*"):
271
+ http_res_text = await utils.stream_to_text_async(http_res)
272
+ raise errors.TrainTravelDefaultError(
273
+ "API error occurred", http_res, http_res_text
274
+ )
275
+ if utils.match_response(http_res, "500", "*"):
276
+ http_res_text = await utils.stream_to_text_async(http_res)
277
+ raise errors.TrainTravelDefaultError(
278
+ "API error occurred", http_res, http_res_text
279
+ )
280
+ if utils.match_response(http_res, "4XX", "*"):
281
+ http_res_text = await utils.stream_to_text_async(http_res)
282
+ raise errors.TrainTravelDefaultError(
283
+ "API error occurred", http_res, http_res_text
284
+ )
285
+ if utils.match_response(http_res, "5XX", "*"):
286
+ http_res_text = await utils.stream_to_text_async(http_res)
287
+ raise errors.TrainTravelDefaultError(
288
+ "API error occurred", http_res, http_res_text
289
+ )
290
+
291
+ raise errors.TrainTravelDefaultError("Unexpected response received", http_res)
@@ -0,0 +1,21 @@
1
+ """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
2
+
3
+ from .basemodel import (
4
+ BaseModel,
5
+ Nullable,
6
+ OptionalNullable,
7
+ UnrecognizedInt,
8
+ UnrecognizedStr,
9
+ UNSET,
10
+ UNSET_SENTINEL,
11
+ )
12
+
13
+ __all__ = [
14
+ "BaseModel",
15
+ "Nullable",
16
+ "OptionalNullable",
17
+ "UnrecognizedInt",
18
+ "UnrecognizedStr",
19
+ "UNSET",
20
+ "UNSET_SENTINEL",
21
+ ]
@@ -0,0 +1,77 @@
1
+ """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
2
+
3
+ from pydantic import ConfigDict, model_serializer
4
+ from pydantic import BaseModel as PydanticBaseModel
5
+ from pydantic_core import core_schema
6
+ from typing import TYPE_CHECKING, Any, Literal, Optional, TypeVar, Union
7
+ from typing_extensions import TypeAliasType, TypeAlias
8
+
9
+
10
+ class BaseModel(PydanticBaseModel):
11
+ model_config = ConfigDict(
12
+ populate_by_name=True, arbitrary_types_allowed=True, protected_namespaces=()
13
+ )
14
+
15
+
16
+ class Unset(BaseModel):
17
+ @model_serializer(mode="plain")
18
+ def serialize_model(self):
19
+ return UNSET_SENTINEL
20
+
21
+ def __bool__(self) -> Literal[False]:
22
+ return False
23
+
24
+
25
+ UNSET = Unset()
26
+ UNSET_SENTINEL = "~?~unset~?~sentinel~?~"
27
+
28
+
29
+ T = TypeVar("T")
30
+ if TYPE_CHECKING:
31
+ Nullable: TypeAlias = Union[T, None]
32
+ OptionalNullable: TypeAlias = Union[Optional[Nullable[T]], Unset]
33
+ else:
34
+ Nullable = TypeAliasType("Nullable", Union[T, None], type_params=(T,))
35
+ OptionalNullable = TypeAliasType(
36
+ "OptionalNullable", Union[Optional[Nullable[T]], Unset], type_params=(T,)
37
+ )
38
+
39
+
40
+ class UnrecognizedStr(str):
41
+ @classmethod
42
+ def __get_pydantic_core_schema__(cls, _source_type: Any, _handler: Any) -> core_schema.CoreSchema:
43
+ # Make UnrecognizedStr only work in lax mode, not strict mode
44
+ # This makes it a "fallback" option when more specific types (like Literals) don't match
45
+ def validate_lax(v: Any) -> 'UnrecognizedStr':
46
+ if isinstance(v, cls):
47
+ return v
48
+ return cls(str(v))
49
+
50
+ # Use lax_or_strict_schema where strict always fails
51
+ # This forces Pydantic to prefer other union members in strict mode
52
+ # and only fall back to UnrecognizedStr in lax mode
53
+ return core_schema.lax_or_strict_schema(
54
+ lax_schema=core_schema.chain_schema([
55
+ core_schema.str_schema(),
56
+ core_schema.no_info_plain_validator_function(validate_lax)
57
+ ]),
58
+ strict_schema=core_schema.none_schema(), # Always fails in strict mode
59
+ )
60
+
61
+
62
+ class UnrecognizedInt(int):
63
+ @classmethod
64
+ def __get_pydantic_core_schema__(cls, _source_type: Any, _handler: Any) -> core_schema.CoreSchema:
65
+ # Make UnrecognizedInt only work in lax mode, not strict mode
66
+ # This makes it a "fallback" option when more specific types (like Literals) don't match
67
+ def validate_lax(v: Any) -> 'UnrecognizedInt':
68
+ if isinstance(v, cls):
69
+ return v
70
+ return cls(int(v))
71
+ return core_schema.lax_or_strict_schema(
72
+ lax_schema=core_schema.chain_schema([
73
+ core_schema.int_schema(),
74
+ core_schema.no_info_plain_validator_function(validate_lax)
75
+ ]),
76
+ strict_schema=core_schema.none_schema(), # Always fails in strict mode
77
+ )
@@ -0,0 +1,206 @@
1
+ """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
2
+
3
+ from typing import TYPE_CHECKING, Callable, TypeVar
4
+ from importlib import import_module
5
+ import asyncio
6
+ import builtins
7
+ import sys
8
+
9
+ _T = TypeVar("_T")
10
+
11
+
12
+ async def run_sync_in_thread(func: Callable[..., _T], *args) -> _T:
13
+ """Run a synchronous function in a thread pool to avoid blocking the event loop."""
14
+ return await asyncio.to_thread(func, *args)
15
+
16
+
17
+ if TYPE_CHECKING:
18
+ from .annotations import get_discriminator
19
+ from .datetimes import parse_datetime
20
+ from .enums import OpenEnumMeta
21
+ from .headers import get_headers, get_response_headers
22
+ from .metadata import (
23
+ FieldMetadata,
24
+ find_metadata,
25
+ FormMetadata,
26
+ HeaderMetadata,
27
+ MultipartFormMetadata,
28
+ PathParamMetadata,
29
+ QueryParamMetadata,
30
+ RequestMetadata,
31
+ SecurityMetadata,
32
+ )
33
+ from .queryparams import get_query_params
34
+ from .retries import BackoffStrategy, Retries, retry, retry_async, RetryConfig
35
+ from .requestbodies import serialize_request_body, SerializedRequestBody
36
+ from .security import get_security, get_security_from_env
37
+
38
+ from .serializers import (
39
+ get_pydantic_model,
40
+ marshal_json,
41
+ unmarshal,
42
+ unmarshal_json,
43
+ serialize_decimal,
44
+ serialize_float,
45
+ serialize_int,
46
+ stream_to_text,
47
+ stream_to_text_async,
48
+ stream_to_bytes,
49
+ stream_to_bytes_async,
50
+ validate_const,
51
+ validate_decimal,
52
+ validate_float,
53
+ validate_int,
54
+ )
55
+ from .url import generate_url, template_url, remove_suffix
56
+ from .values import (
57
+ get_global_from_env,
58
+ match_content_type,
59
+ match_status_codes,
60
+ match_response,
61
+ cast_partial,
62
+ )
63
+ from .logger import Logger, get_body_content, get_default_logger
64
+
65
+ __all__ = [
66
+ "BackoffStrategy",
67
+ "FieldMetadata",
68
+ "find_metadata",
69
+ "FormMetadata",
70
+ "generate_url",
71
+ "get_body_content",
72
+ "get_default_logger",
73
+ "get_discriminator",
74
+ "parse_datetime",
75
+ "get_global_from_env",
76
+ "get_headers",
77
+ "get_pydantic_model",
78
+ "get_query_params",
79
+ "get_response_headers",
80
+ "get_security",
81
+ "get_security_from_env",
82
+ "HeaderMetadata",
83
+ "Logger",
84
+ "marshal_json",
85
+ "match_content_type",
86
+ "match_status_codes",
87
+ "match_response",
88
+ "MultipartFormMetadata",
89
+ "OpenEnumMeta",
90
+ "PathParamMetadata",
91
+ "QueryParamMetadata",
92
+ "remove_suffix",
93
+ "Retries",
94
+ "retry",
95
+ "retry_async",
96
+ "RetryConfig",
97
+ "RequestMetadata",
98
+ "SecurityMetadata",
99
+ "serialize_decimal",
100
+ "serialize_float",
101
+ "serialize_int",
102
+ "serialize_request_body",
103
+ "SerializedRequestBody",
104
+ "stream_to_text",
105
+ "stream_to_text_async",
106
+ "stream_to_bytes",
107
+ "stream_to_bytes_async",
108
+ "template_url",
109
+ "unmarshal",
110
+ "unmarshal_json",
111
+ "validate_decimal",
112
+ "validate_const",
113
+ "validate_float",
114
+ "validate_int",
115
+ "cast_partial",
116
+ ]
117
+
118
+ _dynamic_imports: dict[str, str] = {
119
+ "BackoffStrategy": ".retries",
120
+ "FieldMetadata": ".metadata",
121
+ "find_metadata": ".metadata",
122
+ "FormMetadata": ".metadata",
123
+ "generate_url": ".url",
124
+ "get_body_content": ".logger",
125
+ "get_default_logger": ".logger",
126
+ "get_discriminator": ".annotations",
127
+ "parse_datetime": ".datetimes",
128
+ "get_global_from_env": ".values",
129
+ "get_headers": ".headers",
130
+ "get_pydantic_model": ".serializers",
131
+ "get_query_params": ".queryparams",
132
+ "get_response_headers": ".headers",
133
+ "get_security": ".security",
134
+ "get_security_from_env": ".security",
135
+ "HeaderMetadata": ".metadata",
136
+ "Logger": ".logger",
137
+ "marshal_json": ".serializers",
138
+ "match_content_type": ".values",
139
+ "match_status_codes": ".values",
140
+ "match_response": ".values",
141
+ "MultipartFormMetadata": ".metadata",
142
+ "OpenEnumMeta": ".enums",
143
+ "PathParamMetadata": ".metadata",
144
+ "QueryParamMetadata": ".metadata",
145
+ "remove_suffix": ".url",
146
+ "Retries": ".retries",
147
+ "retry": ".retries",
148
+ "retry_async": ".retries",
149
+ "RetryConfig": ".retries",
150
+ "RequestMetadata": ".metadata",
151
+ "SecurityMetadata": ".metadata",
152
+ "serialize_decimal": ".serializers",
153
+ "serialize_float": ".serializers",
154
+ "serialize_int": ".serializers",
155
+ "serialize_request_body": ".requestbodies",
156
+ "SerializedRequestBody": ".requestbodies",
157
+ "stream_to_text": ".serializers",
158
+ "stream_to_text_async": ".serializers",
159
+ "stream_to_bytes": ".serializers",
160
+ "stream_to_bytes_async": ".serializers",
161
+ "template_url": ".url",
162
+ "unmarshal": ".serializers",
163
+ "unmarshal_json": ".serializers",
164
+ "validate_decimal": ".serializers",
165
+ "validate_const": ".serializers",
166
+ "validate_float": ".serializers",
167
+ "validate_int": ".serializers",
168
+ "cast_partial": ".values",
169
+ }
170
+
171
+
172
+ def dynamic_import(modname, retries=3):
173
+ for attempt in range(retries):
174
+ try:
175
+ return import_module(modname, __package__)
176
+ except KeyError:
177
+ # Clear any half-initialized module and retry
178
+ sys.modules.pop(modname, None)
179
+ if attempt == retries - 1:
180
+ break
181
+ raise KeyError(f"Failed to import module '{modname}' after {retries} attempts")
182
+
183
+
184
+ def __getattr__(attr_name: str) -> object:
185
+ module_name = _dynamic_imports.get(attr_name)
186
+ if module_name is None:
187
+ raise AttributeError(
188
+ f"no {attr_name} found in _dynamic_imports, module name -> {__name__} "
189
+ )
190
+
191
+ try:
192
+ module = dynamic_import(module_name)
193
+ return getattr(module, attr_name)
194
+ except ImportError as e:
195
+ raise ImportError(
196
+ f"Failed to import {attr_name} from {module_name}: {e}"
197
+ ) from e
198
+ except AttributeError as e:
199
+ raise AttributeError(
200
+ f"Failed to get {attr_name} from {module_name}: {e}"
201
+ ) from e
202
+
203
+
204
+ def __dir__():
205
+ lazy_attrs = builtins.list(_dynamic_imports.keys())
206
+ return builtins.sorted(lazy_attrs)
@@ -0,0 +1,79 @@
1
+ """Code generated by Speakeasy (https://speakeasy.com). DO NOT EDIT."""
2
+
3
+ from enum import Enum
4
+ from typing import Any, Optional
5
+
6
+
7
+ def get_discriminator(model: Any, fieldname: str, key: str) -> str:
8
+ """
9
+ Recursively search for the discriminator attribute in a model.
10
+
11
+ Args:
12
+ model (Any): The model to search within.
13
+ fieldname (str): The name of the field to search for.
14
+ key (str): The key to search for in dictionaries.
15
+
16
+ Returns:
17
+ str: The name of the discriminator attribute.
18
+
19
+ Raises:
20
+ ValueError: If the discriminator attribute is not found.
21
+ """
22
+ upper_fieldname = fieldname.upper()
23
+
24
+ def get_field_discriminator(field: Any) -> Optional[str]:
25
+ """Search for the discriminator attribute in a given field."""
26
+
27
+ if isinstance(field, dict):
28
+ if key in field:
29
+ return f"{field[key]}"
30
+
31
+ if hasattr(field, fieldname):
32
+ attr = getattr(field, fieldname)
33
+ if isinstance(attr, Enum):
34
+ return f"{attr.value}"
35
+ return f"{attr}"
36
+
37
+ if hasattr(field, upper_fieldname):
38
+ attr = getattr(field, upper_fieldname)
39
+ if isinstance(attr, Enum):
40
+ return f"{attr.value}"
41
+ return f"{attr}"
42
+
43
+ return None
44
+
45
+ def search_nested_discriminator(obj: Any) -> Optional[str]:
46
+ """Recursively search for discriminator in nested structures."""
47
+ # First try direct field lookup
48
+ discriminator = get_field_discriminator(obj)
49
+ if discriminator is not None:
50
+ return discriminator
51
+
52
+ # If it's a dict, search in nested values
53
+ if isinstance(obj, dict):
54
+ for value in obj.values():
55
+ if isinstance(value, list):
56
+ # Search in list items
57
+ for item in value:
58
+ nested_discriminator = search_nested_discriminator(item)
59
+ if nested_discriminator is not None:
60
+ return nested_discriminator
61
+ elif isinstance(value, dict):
62
+ # Search in nested dict
63
+ nested_discriminator = search_nested_discriminator(value)
64
+ if nested_discriminator is not None:
65
+ return nested_discriminator
66
+
67
+ return None
68
+
69
+ if isinstance(model, list):
70
+ for field in model:
71
+ discriminator = search_nested_discriminator(field)
72
+ if discriminator is not None:
73
+ return discriminator
74
+
75
+ discriminator = search_nested_discriminator(model)
76
+ if discriminator is not None:
77
+ return discriminator
78
+
79
+ raise ValueError(f"Could not find discriminator field {fieldname} in {model}")