redblack-python-sdk 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- redblack/__init__.py +105 -0
- redblack/_generated/__init__.py +39 -0
- redblack/_generated/contract.py +212 -0
- redblack/_generated/operation_specs.py +5115 -0
- redblack/_version.py +4 -0
- redblack/auth.py +352 -0
- redblack/client.py +512 -0
- redblack/config.py +576 -0
- redblack/downloads.py +410 -0
- redblack/errors.py +483 -0
- redblack/models/__init__.py +105 -0
- redblack/models/base.py +150 -0
- redblack/models/generated.py +11736 -0
- redblack/odata.py +75 -0
- redblack/pagination.py +184 -0
- redblack/polling.py +163 -0
- redblack/py.typed +1 -0
- redblack/raw.py +102 -0
- redblack/resources/__init__.py +65 -0
- redblack/resources/_base.py +212 -0
- redblack/resources/_collections.py +80 -0
- redblack/resources/_exports.py +215 -0
- redblack/resources/accounts.py +132 -0
- redblack/resources/cash_actions.py +260 -0
- redblack/resources/equivalents.py +144 -0
- redblack/resources/households.py +138 -0
- redblack/resources/orders.py +118 -0
- redblack/resources/restrictions.py +146 -0
- redblack/resources/review_sets.py +889 -0
- redblack/resources/securities.py +175 -0
- redblack/resources/tasks.py +291 -0
- redblack/resources/users.py +88 -0
- redblack/response.py +118 -0
- redblack/transport.py +606 -0
- redblack_python_sdk-0.1.0.dist-info/METADATA +151 -0
- redblack_python_sdk-0.1.0.dist-info/RECORD +38 -0
- redblack_python_sdk-0.1.0.dist-info/WHEEL +5 -0
- redblack_python_sdk-0.1.0.dist-info/top_level.txt +1 -0
redblack/client.py
ADDED
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
"""Public synchronous RedBlack client."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
from collections.abc import Mapping
|
|
8
|
+
from decimal import Decimal
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from types import TracebackType
|
|
11
|
+
from typing import Any, Literal, Self
|
|
12
|
+
from urllib.parse import quote
|
|
13
|
+
|
|
14
|
+
import httpx
|
|
15
|
+
|
|
16
|
+
from ._generated.contract import (
|
|
17
|
+
encode_request,
|
|
18
|
+
get_operation_spec,
|
|
19
|
+
parse_response,
|
|
20
|
+
)
|
|
21
|
+
from ._version import SUPPORTED_API_VERSION
|
|
22
|
+
from .auth import TokenProvider
|
|
23
|
+
from .config import (
|
|
24
|
+
Environment,
|
|
25
|
+
PasswordCredentials,
|
|
26
|
+
RedBlackConfig,
|
|
27
|
+
RetryConfig,
|
|
28
|
+
TimeoutConfig,
|
|
29
|
+
VerifyValue,
|
|
30
|
+
)
|
|
31
|
+
from .errors import ConfigurationError, ResponseDecodeError
|
|
32
|
+
from .raw import RawClient
|
|
33
|
+
from .resources import (
|
|
34
|
+
AccountsResource,
|
|
35
|
+
CashActionsResource,
|
|
36
|
+
EquivalentsResource,
|
|
37
|
+
HouseholdsResource,
|
|
38
|
+
OrdersResource,
|
|
39
|
+
RestrictionsResource,
|
|
40
|
+
ReviewSetsResource,
|
|
41
|
+
SecuritiesResource,
|
|
42
|
+
TasksResource,
|
|
43
|
+
UsersResource,
|
|
44
|
+
)
|
|
45
|
+
from .response import ApiResponse
|
|
46
|
+
from .transport import HttpxTransport
|
|
47
|
+
|
|
48
|
+
_PATH_PARAMETER = re.compile(r"\{([^{}]+)\}")
|
|
49
|
+
_MANAGED_HEADERS = frozenset(
|
|
50
|
+
{"accept-version", "authorization", "client-id", "content-type", "x-api-key"}
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class RedBlackClient:
|
|
55
|
+
"""Synchronous, typed client for RedBlack API release 2026.3.0.
|
|
56
|
+
|
|
57
|
+
The normal constructor is intentionally explicit and reads neither process
|
|
58
|
+
variables nor dotenv files. Use :meth:`from_env` or :meth:`from_dotenv`
|
|
59
|
+
when configuration lookup is desired.
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
config: RedBlackConfig
|
|
63
|
+
accounts: AccountsResource[Literal[False]]
|
|
64
|
+
households: HouseholdsResource[Literal[False]]
|
|
65
|
+
restrictions: RestrictionsResource[Literal[False]]
|
|
66
|
+
equivalents: EquivalentsResource[Literal[False]]
|
|
67
|
+
cash_actions: CashActionsResource[Literal[False]]
|
|
68
|
+
securities: SecuritiesResource[Literal[False]]
|
|
69
|
+
review_sets: ReviewSetsResource[Literal[False]]
|
|
70
|
+
users: UsersResource[Literal[False]]
|
|
71
|
+
orders: OrdersResource[Literal[False]]
|
|
72
|
+
tasks: TasksResource[Literal[False]]
|
|
73
|
+
with_http_info: HttpInfoClient
|
|
74
|
+
raw: RawClient
|
|
75
|
+
|
|
76
|
+
def __init__(
|
|
77
|
+
self,
|
|
78
|
+
*,
|
|
79
|
+
environment: Environment | str,
|
|
80
|
+
api_key: str,
|
|
81
|
+
client_id: str,
|
|
82
|
+
api_version: str = SUPPORTED_API_VERSION,
|
|
83
|
+
credentials: PasswordCredentials | None = None,
|
|
84
|
+
access_token: str | None = None,
|
|
85
|
+
token_provider: TokenProvider | None = None,
|
|
86
|
+
base_url: str | None = None,
|
|
87
|
+
timeout: TimeoutConfig | None = None,
|
|
88
|
+
retry: RetryConfig | None = None,
|
|
89
|
+
verify: VerifyValue = True,
|
|
90
|
+
proxy: str | None = None,
|
|
91
|
+
trust_env: bool = False,
|
|
92
|
+
allow_insecure_transport: bool = False,
|
|
93
|
+
allow_unsupported_api_version: bool = False,
|
|
94
|
+
http_client: httpx.Client | None = None,
|
|
95
|
+
) -> None:
|
|
96
|
+
if token_provider is not None and (credentials is not None or access_token is not None):
|
|
97
|
+
raise ConfigurationError(
|
|
98
|
+
"token_provider cannot be combined with credentials or access_token"
|
|
99
|
+
)
|
|
100
|
+
config = RedBlackConfig(
|
|
101
|
+
environment=environment,
|
|
102
|
+
api_key=api_key,
|
|
103
|
+
client_id=client_id,
|
|
104
|
+
api_version=api_version,
|
|
105
|
+
credentials=credentials,
|
|
106
|
+
access_token=access_token,
|
|
107
|
+
base_url=base_url,
|
|
108
|
+
timeout=timeout or TimeoutConfig(),
|
|
109
|
+
retry=retry or RetryConfig(),
|
|
110
|
+
verify=verify,
|
|
111
|
+
proxy=proxy,
|
|
112
|
+
trust_env=trust_env,
|
|
113
|
+
allow_insecure_transport=allow_insecure_transport,
|
|
114
|
+
allow_unsupported_api_version=allow_unsupported_api_version,
|
|
115
|
+
)
|
|
116
|
+
self._initialize(
|
|
117
|
+
config,
|
|
118
|
+
token_provider=token_provider,
|
|
119
|
+
http_client=http_client,
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
@classmethod
|
|
123
|
+
def from_env(
|
|
124
|
+
cls,
|
|
125
|
+
*,
|
|
126
|
+
environ: Mapping[str, str] | None = None,
|
|
127
|
+
token_provider: TokenProvider | None = None,
|
|
128
|
+
http_client: httpx.Client | None = None,
|
|
129
|
+
**overrides: Any,
|
|
130
|
+
) -> Self:
|
|
131
|
+
"""Build a client from explicit overrides, then process variables."""
|
|
132
|
+
|
|
133
|
+
config = RedBlackConfig.from_env(
|
|
134
|
+
environ=environ,
|
|
135
|
+
require_auth=token_provider is None,
|
|
136
|
+
**overrides,
|
|
137
|
+
)
|
|
138
|
+
return cls._from_config(
|
|
139
|
+
config,
|
|
140
|
+
token_provider=token_provider,
|
|
141
|
+
http_client=http_client,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
@classmethod
|
|
145
|
+
def from_dotenv(
|
|
146
|
+
cls,
|
|
147
|
+
path: str | os.PathLike[str] | None = None,
|
|
148
|
+
*,
|
|
149
|
+
search_start: str | os.PathLike[str] | None = None,
|
|
150
|
+
environ: Mapping[str, str] | None = None,
|
|
151
|
+
token_provider: TokenProvider | None = None,
|
|
152
|
+
http_client: httpx.Client | None = None,
|
|
153
|
+
**overrides: Any,
|
|
154
|
+
) -> Self:
|
|
155
|
+
"""Build a client with explicit > environment > dotenv precedence."""
|
|
156
|
+
|
|
157
|
+
config = RedBlackConfig.from_dotenv(
|
|
158
|
+
path,
|
|
159
|
+
search_start=search_start,
|
|
160
|
+
environ=environ,
|
|
161
|
+
require_auth=token_provider is None,
|
|
162
|
+
**overrides,
|
|
163
|
+
)
|
|
164
|
+
return cls._from_config(
|
|
165
|
+
config,
|
|
166
|
+
token_provider=token_provider,
|
|
167
|
+
http_client=http_client,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def _from_config(
|
|
172
|
+
cls,
|
|
173
|
+
config: RedBlackConfig,
|
|
174
|
+
*,
|
|
175
|
+
token_provider: TokenProvider | None,
|
|
176
|
+
http_client: httpx.Client | None,
|
|
177
|
+
) -> Self:
|
|
178
|
+
instance = cls.__new__(cls)
|
|
179
|
+
instance._initialize(
|
|
180
|
+
config,
|
|
181
|
+
token_provider=token_provider,
|
|
182
|
+
http_client=http_client,
|
|
183
|
+
)
|
|
184
|
+
return instance
|
|
185
|
+
|
|
186
|
+
def _initialize(
|
|
187
|
+
self,
|
|
188
|
+
config: RedBlackConfig,
|
|
189
|
+
*,
|
|
190
|
+
token_provider: TokenProvider | None,
|
|
191
|
+
http_client: httpx.Client | None,
|
|
192
|
+
) -> None:
|
|
193
|
+
if token_provider is not None and config.auth_mode is not None:
|
|
194
|
+
raise ConfigurationError(
|
|
195
|
+
"token_provider cannot be combined with built-in authentication"
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
owns_token_provider = token_provider is None
|
|
199
|
+
resolved_provider = token_provider or config.build_token_provider(http_client=http_client)
|
|
200
|
+
try:
|
|
201
|
+
transport = HttpxTransport(
|
|
202
|
+
config,
|
|
203
|
+
resolved_provider,
|
|
204
|
+
client=http_client,
|
|
205
|
+
)
|
|
206
|
+
except BaseException:
|
|
207
|
+
if owns_token_provider:
|
|
208
|
+
close = getattr(resolved_provider, "close", None)
|
|
209
|
+
if callable(close):
|
|
210
|
+
close()
|
|
211
|
+
raise
|
|
212
|
+
|
|
213
|
+
self.config = config
|
|
214
|
+
self._token_provider = resolved_provider
|
|
215
|
+
self._owns_token_provider = owns_token_provider
|
|
216
|
+
self._transport = transport
|
|
217
|
+
self._closed = False
|
|
218
|
+
self.raw = RawClient(transport)
|
|
219
|
+
self.accounts = AccountsResource(self)
|
|
220
|
+
self.households = HouseholdsResource(self)
|
|
221
|
+
self.restrictions = RestrictionsResource(self)
|
|
222
|
+
self.equivalents = EquivalentsResource(self)
|
|
223
|
+
self.cash_actions = CashActionsResource(self)
|
|
224
|
+
self.securities = SecuritiesResource(self)
|
|
225
|
+
self.review_sets = ReviewSetsResource(self)
|
|
226
|
+
self.users = UsersResource(self)
|
|
227
|
+
self.orders = OrdersResource(self)
|
|
228
|
+
self.tasks = TasksResource(self)
|
|
229
|
+
self.with_http_info = HttpInfoClient(self)
|
|
230
|
+
|
|
231
|
+
def request(
|
|
232
|
+
self,
|
|
233
|
+
operation_id: str,
|
|
234
|
+
*,
|
|
235
|
+
path_params: Mapping[str, object] | None = None,
|
|
236
|
+
query: Mapping[str, object] | None = None,
|
|
237
|
+
headers: Mapping[str, str] | None = None,
|
|
238
|
+
json: object | None = None,
|
|
239
|
+
stream: bool = False,
|
|
240
|
+
timeout_budget: float | None = None,
|
|
241
|
+
) -> ApiResponse[Any]:
|
|
242
|
+
"""Execute one captured operation and return data with HTTP metadata.
|
|
243
|
+
|
|
244
|
+
Resource facades call this operation-level method. It is also useful to
|
|
245
|
+
advanced callers who know a RedBlack ``operationId`` but want to retain
|
|
246
|
+
generated validation and response parsing.
|
|
247
|
+
|
|
248
|
+
``timeout_budget`` limits transport work for this call in seconds.
|
|
249
|
+
"""
|
|
250
|
+
|
|
251
|
+
if self.config.api_version != SUPPORTED_API_VERSION:
|
|
252
|
+
raise ConfigurationError(
|
|
253
|
+
f"typed calls require API {SUPPORTED_API_VERSION}; "
|
|
254
|
+
"use client.raw for an explicitly unsupported version"
|
|
255
|
+
)
|
|
256
|
+
if not isinstance(stream, bool):
|
|
257
|
+
raise TypeError("stream must be a bool")
|
|
258
|
+
|
|
259
|
+
operation = get_operation_spec(operation_id)
|
|
260
|
+
path = _render_path(operation.path, operation.parameters, path_params)
|
|
261
|
+
params = _render_query(operation.parameters, query)
|
|
262
|
+
encoded = encode_request(operation_id, json)
|
|
263
|
+
request_headers = _render_headers(
|
|
264
|
+
operation.parameters,
|
|
265
|
+
headers,
|
|
266
|
+
has_body=encoded is not None,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
raw_response = self._transport.request(
|
|
270
|
+
operation.method,
|
|
271
|
+
path,
|
|
272
|
+
params=params,
|
|
273
|
+
headers=request_headers,
|
|
274
|
+
content=encoded,
|
|
275
|
+
expected_statuses=operation.success_statuses,
|
|
276
|
+
authenticated=True,
|
|
277
|
+
operation_id=operation_id,
|
|
278
|
+
timeout_budget=timeout_budget,
|
|
279
|
+
)
|
|
280
|
+
content_type = raw_response.headers.get("content-type")
|
|
281
|
+
try:
|
|
282
|
+
parsed = parse_response(
|
|
283
|
+
operation_id,
|
|
284
|
+
raw_response.status_code,
|
|
285
|
+
raw_response.raw,
|
|
286
|
+
content_type,
|
|
287
|
+
)
|
|
288
|
+
except (TypeError, ValueError):
|
|
289
|
+
raise ResponseDecodeError(
|
|
290
|
+
"RedBlack success response did not match the captured contract",
|
|
291
|
+
status_code=raw_response.status_code,
|
|
292
|
+
method=operation.method,
|
|
293
|
+
path=operation_id,
|
|
294
|
+
request_id=raw_response.request_id,
|
|
295
|
+
raw_body=raw_response.raw,
|
|
296
|
+
) from None
|
|
297
|
+
|
|
298
|
+
attach_http = getattr(parsed, "_attach_http", None)
|
|
299
|
+
if callable(attach_http):
|
|
300
|
+
parsed = attach_http(raw_response.http)
|
|
301
|
+
return raw_response.with_data(parsed)
|
|
302
|
+
|
|
303
|
+
def close(self) -> None:
|
|
304
|
+
"""Close SDK-owned HTTP resources; caller-injected clients stay open."""
|
|
305
|
+
|
|
306
|
+
if self._closed:
|
|
307
|
+
return
|
|
308
|
+
self._closed = True
|
|
309
|
+
self._transport.close()
|
|
310
|
+
if self._owns_token_provider:
|
|
311
|
+
close = getattr(self._token_provider, "close", None)
|
|
312
|
+
if callable(close):
|
|
313
|
+
close()
|
|
314
|
+
|
|
315
|
+
def __enter__(self) -> Self:
|
|
316
|
+
return self
|
|
317
|
+
|
|
318
|
+
def __exit__(
|
|
319
|
+
self,
|
|
320
|
+
exc_type: type[BaseException] | None,
|
|
321
|
+
exc_value: BaseException | None,
|
|
322
|
+
traceback: TracebackType | None,
|
|
323
|
+
) -> None:
|
|
324
|
+
del exc_type, exc_value, traceback
|
|
325
|
+
self.close()
|
|
326
|
+
|
|
327
|
+
def __repr__(self) -> str:
|
|
328
|
+
return f"RedBlackClient(config={self.config!r}, closed={self._closed!r})"
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
class HttpInfoClient:
|
|
332
|
+
"""View whose direct resource requests return :class:`ApiResponse`.
|
|
333
|
+
|
|
334
|
+
Lazy iterator helpers still yield their documented pages or item values.
|
|
335
|
+
"""
|
|
336
|
+
|
|
337
|
+
accounts: AccountsResource[Literal[True]]
|
|
338
|
+
households: HouseholdsResource[Literal[True]]
|
|
339
|
+
restrictions: RestrictionsResource[Literal[True]]
|
|
340
|
+
equivalents: EquivalentsResource[Literal[True]]
|
|
341
|
+
cash_actions: CashActionsResource[Literal[True]]
|
|
342
|
+
securities: SecuritiesResource[Literal[True]]
|
|
343
|
+
review_sets: ReviewSetsResource[Literal[True]]
|
|
344
|
+
users: UsersResource[Literal[True]]
|
|
345
|
+
orders: OrdersResource[Literal[True]]
|
|
346
|
+
tasks: TasksResource[Literal[True]]
|
|
347
|
+
|
|
348
|
+
def __init__(self, client: RedBlackClient) -> None:
|
|
349
|
+
self._client = client
|
|
350
|
+
self.accounts = AccountsResource(client, with_http_info=True)
|
|
351
|
+
self.households = HouseholdsResource(client, with_http_info=True)
|
|
352
|
+
self.restrictions = RestrictionsResource(client, with_http_info=True)
|
|
353
|
+
self.equivalents = EquivalentsResource(client, with_http_info=True)
|
|
354
|
+
self.cash_actions = CashActionsResource(client, with_http_info=True)
|
|
355
|
+
self.securities = SecuritiesResource(client, with_http_info=True)
|
|
356
|
+
self.review_sets = ReviewSetsResource(client, with_http_info=True)
|
|
357
|
+
self.users = UsersResource(client, with_http_info=True)
|
|
358
|
+
self.orders = OrdersResource(client, with_http_info=True)
|
|
359
|
+
self.tasks = TasksResource(client, with_http_info=True)
|
|
360
|
+
|
|
361
|
+
def request(
|
|
362
|
+
self,
|
|
363
|
+
operation_id: str,
|
|
364
|
+
*,
|
|
365
|
+
path_params: Mapping[str, object] | None = None,
|
|
366
|
+
query: Mapping[str, object] | None = None,
|
|
367
|
+
headers: Mapping[str, str] | None = None,
|
|
368
|
+
json: object | None = None,
|
|
369
|
+
stream: bool = False,
|
|
370
|
+
timeout_budget: float | None = None,
|
|
371
|
+
) -> ApiResponse[Any]:
|
|
372
|
+
"""Delegate an operation-level request to the owning client."""
|
|
373
|
+
|
|
374
|
+
return self._client.request(
|
|
375
|
+
operation_id,
|
|
376
|
+
path_params=path_params,
|
|
377
|
+
query=query,
|
|
378
|
+
headers=headers,
|
|
379
|
+
json=json,
|
|
380
|
+
stream=stream,
|
|
381
|
+
timeout_budget=timeout_budget,
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
def _render_path(
|
|
386
|
+
template: str,
|
|
387
|
+
parameters: tuple[Any, ...],
|
|
388
|
+
supplied: Mapping[str, object] | None,
|
|
389
|
+
) -> str:
|
|
390
|
+
expected = _PATH_PARAMETER.findall(template)
|
|
391
|
+
values = dict(supplied or {})
|
|
392
|
+
aliases = {
|
|
393
|
+
parameter.python_name: parameter.wire_name
|
|
394
|
+
for parameter in parameters
|
|
395
|
+
if parameter.location == "path" and parameter.python_name != parameter.wire_name
|
|
396
|
+
}
|
|
397
|
+
for python_name, wire_name in aliases.items():
|
|
398
|
+
if python_name in values:
|
|
399
|
+
if wire_name in values:
|
|
400
|
+
raise TypeError(
|
|
401
|
+
f"path parameter {wire_name!r} was supplied by both wire and Python name"
|
|
402
|
+
)
|
|
403
|
+
values[wire_name] = values.pop(python_name)
|
|
404
|
+
|
|
405
|
+
missing = [name for name in expected if name not in values]
|
|
406
|
+
extra = sorted(set(values) - set(expected))
|
|
407
|
+
if missing:
|
|
408
|
+
raise TypeError(f"missing path parameter(s): {', '.join(missing)}")
|
|
409
|
+
if extra:
|
|
410
|
+
raise TypeError(f"unknown path parameter(s): {', '.join(extra)}")
|
|
411
|
+
|
|
412
|
+
def replacement(match: re.Match[str]) -> str:
|
|
413
|
+
name = match.group(1)
|
|
414
|
+
value = values[name]
|
|
415
|
+
if value is None:
|
|
416
|
+
raise TypeError(f"path parameter {name!r} cannot be None")
|
|
417
|
+
if isinstance(value, Enum):
|
|
418
|
+
value = value.value
|
|
419
|
+
return quote(str(value), safe="-._~")
|
|
420
|
+
|
|
421
|
+
return _PATH_PARAMETER.sub(replacement, template)
|
|
422
|
+
|
|
423
|
+
|
|
424
|
+
def _render_query(
|
|
425
|
+
parameters: tuple[Any, ...],
|
|
426
|
+
supplied: Mapping[str, object] | None,
|
|
427
|
+
) -> dict[str, object]:
|
|
428
|
+
values = dict(supplied or {})
|
|
429
|
+
query_parameters = {
|
|
430
|
+
parameter.wire_name: parameter for parameter in parameters if parameter.location == "query"
|
|
431
|
+
}
|
|
432
|
+
aliases = {
|
|
433
|
+
parameter.python_name: parameter.wire_name
|
|
434
|
+
for parameter in query_parameters.values()
|
|
435
|
+
if parameter.python_name != parameter.wire_name
|
|
436
|
+
}
|
|
437
|
+
rendered: dict[str, object] = {}
|
|
438
|
+
for name, value in values.items():
|
|
439
|
+
wire_name = aliases.get(name, name)
|
|
440
|
+
if wire_name not in query_parameters:
|
|
441
|
+
raise TypeError(f"unknown query parameter: {name}")
|
|
442
|
+
if wire_name in rendered:
|
|
443
|
+
raise TypeError(f"query parameter {wire_name!r} was supplied twice")
|
|
444
|
+
if value is None:
|
|
445
|
+
continue
|
|
446
|
+
if isinstance(value, bool):
|
|
447
|
+
rendered[wire_name] = "true" if value else "false"
|
|
448
|
+
elif isinstance(value, Enum):
|
|
449
|
+
rendered[wire_name] = value.value
|
|
450
|
+
elif isinstance(value, Decimal):
|
|
451
|
+
rendered[wire_name] = str(value)
|
|
452
|
+
else:
|
|
453
|
+
rendered[wire_name] = value
|
|
454
|
+
|
|
455
|
+
missing = [
|
|
456
|
+
name
|
|
457
|
+
for name, parameter in query_parameters.items()
|
|
458
|
+
if parameter.required and name not in rendered
|
|
459
|
+
]
|
|
460
|
+
if missing:
|
|
461
|
+
raise TypeError(f"missing query parameter(s): {', '.join(missing)}")
|
|
462
|
+
return rendered
|
|
463
|
+
|
|
464
|
+
|
|
465
|
+
def _render_headers(
|
|
466
|
+
parameters: tuple[Any, ...],
|
|
467
|
+
supplied: Mapping[str, str] | None,
|
|
468
|
+
*,
|
|
469
|
+
has_body: bool,
|
|
470
|
+
) -> dict[str, str]:
|
|
471
|
+
header_parameters = {
|
|
472
|
+
parameter.wire_name.casefold(): parameter
|
|
473
|
+
for parameter in parameters
|
|
474
|
+
if parameter.location == "header"
|
|
475
|
+
}
|
|
476
|
+
aliases = {
|
|
477
|
+
parameter.python_name: parameter.wire_name
|
|
478
|
+
for parameter in header_parameters.values()
|
|
479
|
+
if parameter.python_name != parameter.wire_name
|
|
480
|
+
}
|
|
481
|
+
rendered: dict[str, str] = {}
|
|
482
|
+
rendered_names: set[str] = set()
|
|
483
|
+
for name, value in (supplied or {}).items():
|
|
484
|
+
if not isinstance(name, str) or not isinstance(value, str):
|
|
485
|
+
raise TypeError("header names and values must be strings")
|
|
486
|
+
if name.casefold() in _MANAGED_HEADERS:
|
|
487
|
+
raise ConfigurationError(f"request header {name!r} is managed by the SDK")
|
|
488
|
+
wire_name = aliases.get(name, name)
|
|
489
|
+
parameter = header_parameters.get(wire_name.casefold())
|
|
490
|
+
if parameter is None:
|
|
491
|
+
raise TypeError(f"unknown operation header: {name}")
|
|
492
|
+
normalized = parameter.wire_name.casefold()
|
|
493
|
+
if normalized in rendered_names:
|
|
494
|
+
raise TypeError(f"header {parameter.wire_name!r} was supplied twice")
|
|
495
|
+
rendered[parameter.wire_name] = value
|
|
496
|
+
rendered_names.add(normalized)
|
|
497
|
+
|
|
498
|
+
missing = [
|
|
499
|
+
parameter.wire_name
|
|
500
|
+
for parameter in header_parameters.values()
|
|
501
|
+
if parameter.required
|
|
502
|
+
and parameter.wire_name.casefold() not in _MANAGED_HEADERS
|
|
503
|
+
and parameter.wire_name.casefold() not in rendered_names
|
|
504
|
+
]
|
|
505
|
+
if missing:
|
|
506
|
+
raise TypeError(f"missing operation header(s): {', '.join(missing)}")
|
|
507
|
+
if has_body:
|
|
508
|
+
rendered["Content-Type"] = "application/json"
|
|
509
|
+
return rendered
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
__all__ = ["HttpInfoClient", "RedBlackClient"]
|