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/__init__.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""Typed synchronous SDK for the RedBlack API."""
|
|
2
|
+
|
|
3
|
+
from ._version import SUPPORTED_API_VERSION, __version__
|
|
4
|
+
from .auth import (
|
|
5
|
+
PasswordCredentials,
|
|
6
|
+
PasswordGrantTokenProvider,
|
|
7
|
+
StaticTokenProvider,
|
|
8
|
+
TokenProvider,
|
|
9
|
+
)
|
|
10
|
+
from .client import HttpInfoClient, RedBlackClient
|
|
11
|
+
from .config import Environment, RedBlackConfig, RetryConfig, TimeoutConfig
|
|
12
|
+
from .downloads import (
|
|
13
|
+
DownloadError,
|
|
14
|
+
DownloadExistsError,
|
|
15
|
+
DownloadResult,
|
|
16
|
+
DownloadSecurityError,
|
|
17
|
+
StorageUrlPolicy,
|
|
18
|
+
)
|
|
19
|
+
from .errors import (
|
|
20
|
+
ApiError,
|
|
21
|
+
AuthenticationError,
|
|
22
|
+
BadRequestError,
|
|
23
|
+
ConfigurationError,
|
|
24
|
+
ConflictError,
|
|
25
|
+
ForbiddenError,
|
|
26
|
+
GoneError,
|
|
27
|
+
MethodNotAllowedError,
|
|
28
|
+
NotFoundError,
|
|
29
|
+
OperationFailedError,
|
|
30
|
+
OperationNeedsInputError,
|
|
31
|
+
PollingError,
|
|
32
|
+
PollTimeoutError,
|
|
33
|
+
PreconditionFailedError,
|
|
34
|
+
RateLimitError,
|
|
35
|
+
RedBlackError,
|
|
36
|
+
RequestTimeoutError,
|
|
37
|
+
ResponseDecodeError,
|
|
38
|
+
ServerError,
|
|
39
|
+
TransportError,
|
|
40
|
+
UnauthorizedError,
|
|
41
|
+
UnprocessableContentError,
|
|
42
|
+
)
|
|
43
|
+
from .odata import ODataQuery
|
|
44
|
+
from .pagination import Page
|
|
45
|
+
from .resources import (
|
|
46
|
+
CONFIRM_DELETE_ALL_CASH_ACTIONS,
|
|
47
|
+
ExportJobHandle,
|
|
48
|
+
OrderAddSessionHandle,
|
|
49
|
+
ReleaseCriteria,
|
|
50
|
+
ReleaseSessionHandle,
|
|
51
|
+
VersionedResource,
|
|
52
|
+
)
|
|
53
|
+
from .response import ApiResponse, HttpMetadata
|
|
54
|
+
|
|
55
|
+
__all__ = [
|
|
56
|
+
"ApiError",
|
|
57
|
+
"ApiResponse",
|
|
58
|
+
"AuthenticationError",
|
|
59
|
+
"BadRequestError",
|
|
60
|
+
"CONFIRM_DELETE_ALL_CASH_ACTIONS",
|
|
61
|
+
"ConfigurationError",
|
|
62
|
+
"ConflictError",
|
|
63
|
+
"DownloadError",
|
|
64
|
+
"DownloadExistsError",
|
|
65
|
+
"DownloadResult",
|
|
66
|
+
"DownloadSecurityError",
|
|
67
|
+
"Environment",
|
|
68
|
+
"ExportJobHandle",
|
|
69
|
+
"ForbiddenError",
|
|
70
|
+
"GoneError",
|
|
71
|
+
"HttpInfoClient",
|
|
72
|
+
"HttpMetadata",
|
|
73
|
+
"MethodNotAllowedError",
|
|
74
|
+
"NotFoundError",
|
|
75
|
+
"ODataQuery",
|
|
76
|
+
"OperationFailedError",
|
|
77
|
+
"OperationNeedsInputError",
|
|
78
|
+
"OrderAddSessionHandle",
|
|
79
|
+
"Page",
|
|
80
|
+
"PasswordCredentials",
|
|
81
|
+
"PasswordGrantTokenProvider",
|
|
82
|
+
"PollingError",
|
|
83
|
+
"PollTimeoutError",
|
|
84
|
+
"PreconditionFailedError",
|
|
85
|
+
"RateLimitError",
|
|
86
|
+
"RedBlackClient",
|
|
87
|
+
"RedBlackConfig",
|
|
88
|
+
"RedBlackError",
|
|
89
|
+
"ReleaseCriteria",
|
|
90
|
+
"ReleaseSessionHandle",
|
|
91
|
+
"RequestTimeoutError",
|
|
92
|
+
"ResponseDecodeError",
|
|
93
|
+
"RetryConfig",
|
|
94
|
+
"SUPPORTED_API_VERSION",
|
|
95
|
+
"ServerError",
|
|
96
|
+
"StaticTokenProvider",
|
|
97
|
+
"StorageUrlPolicy",
|
|
98
|
+
"TimeoutConfig",
|
|
99
|
+
"TokenProvider",
|
|
100
|
+
"TransportError",
|
|
101
|
+
"UnauthorizedError",
|
|
102
|
+
"UnprocessableContentError",
|
|
103
|
+
"VersionedResource",
|
|
104
|
+
"__version__",
|
|
105
|
+
]
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"""Generated RedBlack 2026.3.0 contract registry."""
|
|
2
|
+
|
|
3
|
+
from .contract import (
|
|
4
|
+
OPERATIONS,
|
|
5
|
+
REQUEST_MODELS,
|
|
6
|
+
RESPONSE_MODELS,
|
|
7
|
+
ContractLookupError,
|
|
8
|
+
encode_request,
|
|
9
|
+
get_operation_spec,
|
|
10
|
+
parse_response,
|
|
11
|
+
validate_request,
|
|
12
|
+
)
|
|
13
|
+
from .operation_specs import (
|
|
14
|
+
API_RELEASE,
|
|
15
|
+
OPERATION_IDS,
|
|
16
|
+
MediaTypeSpec,
|
|
17
|
+
OperationSpec,
|
|
18
|
+
ParameterSpec,
|
|
19
|
+
RequestBodySpec,
|
|
20
|
+
ResponseSpec,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
__all__ = [
|
|
24
|
+
"API_RELEASE",
|
|
25
|
+
"ContractLookupError",
|
|
26
|
+
"MediaTypeSpec",
|
|
27
|
+
"OPERATION_IDS",
|
|
28
|
+
"OPERATIONS",
|
|
29
|
+
"OperationSpec",
|
|
30
|
+
"ParameterSpec",
|
|
31
|
+
"REQUEST_MODELS",
|
|
32
|
+
"RESPONSE_MODELS",
|
|
33
|
+
"RequestBodySpec",
|
|
34
|
+
"ResponseSpec",
|
|
35
|
+
"encode_request",
|
|
36
|
+
"get_operation_spec",
|
|
37
|
+
"parse_response",
|
|
38
|
+
"validate_request",
|
|
39
|
+
]
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# This file is generated by tools/generate_sdk_models.py.
|
|
2
|
+
# Do not edit it by hand.
|
|
3
|
+
"""Stable runtime helpers over generated RedBlack operation metadata."""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import json
|
|
8
|
+
import math
|
|
9
|
+
from datetime import date, datetime
|
|
10
|
+
from decimal import Decimal
|
|
11
|
+
from enum import Enum
|
|
12
|
+
|
|
13
|
+
from pydantic import BaseModel
|
|
14
|
+
|
|
15
|
+
from .operation_specs import (
|
|
16
|
+
OPERATIONS,
|
|
17
|
+
MediaTypeSpec,
|
|
18
|
+
OperationSpec,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ContractLookupError(ValueError):
|
|
23
|
+
"""Raised when a call cannot be represented by the captured contract."""
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_operation_spec(operation_id: str) -> OperationSpec:
|
|
27
|
+
"""Return exact metadata for one captured operation."""
|
|
28
|
+
|
|
29
|
+
try:
|
|
30
|
+
return OPERATIONS[operation_id]
|
|
31
|
+
except KeyError as error:
|
|
32
|
+
raise ContractLookupError(f"unknown RedBlack operation: {operation_id}") from error
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _normalize_media_type(value: str | None) -> str | None:
|
|
36
|
+
if value is None:
|
|
37
|
+
return None
|
|
38
|
+
return value.partition(";")[0].strip().lower() or None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _select_media(
|
|
42
|
+
media_types: tuple[MediaTypeSpec, ...],
|
|
43
|
+
requested: str | None,
|
|
44
|
+
) -> MediaTypeSpec | None:
|
|
45
|
+
if not media_types:
|
|
46
|
+
return None
|
|
47
|
+
normalized = _normalize_media_type(requested)
|
|
48
|
+
if normalized is not None:
|
|
49
|
+
for media in media_types:
|
|
50
|
+
if media.media_type.lower() == normalized:
|
|
51
|
+
return media
|
|
52
|
+
if normalized.endswith("+json") or normalized == "application/json":
|
|
53
|
+
json_media = [
|
|
54
|
+
media
|
|
55
|
+
for media in media_types
|
|
56
|
+
if media.media_type.lower().endswith("+json")
|
|
57
|
+
or media.media_type.lower() == "application/json"
|
|
58
|
+
]
|
|
59
|
+
if len(json_media) == 1:
|
|
60
|
+
return json_media[0]
|
|
61
|
+
raise ContractLookupError(
|
|
62
|
+
f"content type {requested!r} is not documented; "
|
|
63
|
+
f"expected {[item.media_type for item in media_types]!r}"
|
|
64
|
+
)
|
|
65
|
+
if len(media_types) == 1:
|
|
66
|
+
return media_types[0]
|
|
67
|
+
raise ContractLookupError(
|
|
68
|
+
"content type is required when an operation documents multiple representations"
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def validate_request(
|
|
73
|
+
operation_id: str,
|
|
74
|
+
value: object,
|
|
75
|
+
*,
|
|
76
|
+
media_type: str = "application/json",
|
|
77
|
+
) -> object | None:
|
|
78
|
+
"""Validate and alias an outbound body while retaining exact ``Decimal`` values."""
|
|
79
|
+
|
|
80
|
+
operation = get_operation_spec(operation_id)
|
|
81
|
+
body = operation.request_body
|
|
82
|
+
if body is None:
|
|
83
|
+
if value is not None:
|
|
84
|
+
raise ContractLookupError(f"{operation_id} does not document a request body")
|
|
85
|
+
return None
|
|
86
|
+
if value is None:
|
|
87
|
+
if body.required:
|
|
88
|
+
raise ContractLookupError(f"{operation_id} requires a request body")
|
|
89
|
+
return None
|
|
90
|
+
selected = _select_media(body.media_types, media_type)
|
|
91
|
+
if selected is None:
|
|
92
|
+
raise ContractLookupError(f"{operation_id} has no typed request representation")
|
|
93
|
+
validated = selected.adapter.validate_python(value)
|
|
94
|
+
return selected.adapter.dump_python(
|
|
95
|
+
validated,
|
|
96
|
+
mode="python",
|
|
97
|
+
by_alias=True,
|
|
98
|
+
exclude_unset=True,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _encode_json_value(value: object) -> str:
|
|
103
|
+
if value is None:
|
|
104
|
+
return "null"
|
|
105
|
+
if value is True:
|
|
106
|
+
return "true"
|
|
107
|
+
if value is False:
|
|
108
|
+
return "false"
|
|
109
|
+
if isinstance(value, Enum):
|
|
110
|
+
return _encode_json_value(value.value)
|
|
111
|
+
if isinstance(value, Decimal):
|
|
112
|
+
if not value.is_finite():
|
|
113
|
+
raise ValueError("non-finite Decimal values are not valid JSON")
|
|
114
|
+
return str(value)
|
|
115
|
+
if isinstance(value, int):
|
|
116
|
+
return str(value)
|
|
117
|
+
if isinstance(value, float):
|
|
118
|
+
if not math.isfinite(value):
|
|
119
|
+
raise ValueError("non-finite float values are not valid JSON")
|
|
120
|
+
return json.dumps(value, ensure_ascii=False, allow_nan=False)
|
|
121
|
+
if isinstance(value, str):
|
|
122
|
+
return json.dumps(value, ensure_ascii=False)
|
|
123
|
+
if isinstance(value, datetime):
|
|
124
|
+
if value.tzinfo is None or value.utcoffset() is None:
|
|
125
|
+
raise ValueError("request datetimes must include an explicit UTC offset")
|
|
126
|
+
return json.dumps(value.isoformat(), ensure_ascii=False)
|
|
127
|
+
if isinstance(value, date):
|
|
128
|
+
return json.dumps(value.isoformat(), ensure_ascii=False)
|
|
129
|
+
if isinstance(value, BaseModel):
|
|
130
|
+
value = value.model_dump(mode="python", by_alias=True, exclude_unset=True)
|
|
131
|
+
if isinstance(value, dict):
|
|
132
|
+
pieces = []
|
|
133
|
+
for key, child in value.items():
|
|
134
|
+
if not isinstance(key, str):
|
|
135
|
+
raise TypeError("JSON object keys must be strings")
|
|
136
|
+
pieces.append(json.dumps(key, ensure_ascii=False) + ":" + _encode_json_value(child))
|
|
137
|
+
return "{" + ",".join(pieces) + "}"
|
|
138
|
+
if isinstance(value, list | tuple):
|
|
139
|
+
return "[" + ",".join(_encode_json_value(child) for child in value) + "]"
|
|
140
|
+
raise TypeError(f"value of type {type(value).__name__} is not JSON serializable")
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def encode_request(
|
|
144
|
+
operation_id: str,
|
|
145
|
+
value: object,
|
|
146
|
+
*,
|
|
147
|
+
media_type: str = "application/json",
|
|
148
|
+
) -> bytes | None:
|
|
149
|
+
"""Validate and encode a body without routing decimal tokens through ``float``."""
|
|
150
|
+
|
|
151
|
+
validated = validate_request(operation_id, value, media_type=media_type)
|
|
152
|
+
if validated is None:
|
|
153
|
+
return None
|
|
154
|
+
return _encode_json_value(validated).encode("utf-8")
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def parse_response(
|
|
158
|
+
operation_id: str,
|
|
159
|
+
status_code: int,
|
|
160
|
+
raw_bytes: bytes,
|
|
161
|
+
content_type: str | None,
|
|
162
|
+
) -> object | None:
|
|
163
|
+
"""Parse directly from response bytes using the documented status schema."""
|
|
164
|
+
|
|
165
|
+
operation = get_operation_spec(operation_id)
|
|
166
|
+
response = operation.response(status_code)
|
|
167
|
+
if response is None:
|
|
168
|
+
raise ContractLookupError(f"{operation_id} does not document response status {status_code}")
|
|
169
|
+
selected = _select_media(response.media_types, content_type)
|
|
170
|
+
if selected is None:
|
|
171
|
+
return None
|
|
172
|
+
if not raw_bytes.strip():
|
|
173
|
+
raise ContractLookupError(
|
|
174
|
+
f"{operation_id} response {status_code} documents "
|
|
175
|
+
f"{selected.media_type} but returned an empty body"
|
|
176
|
+
)
|
|
177
|
+
|
|
178
|
+
# The minimum supported Pydantic 2 releases route JSON numeric tokens
|
|
179
|
+
# through binary float before Decimal validation. Decode with the standard
|
|
180
|
+
# library's exact parse hook first so vendor-decimal digits are retained.
|
|
181
|
+
def reject_non_finite(value: str) -> object:
|
|
182
|
+
raise ValueError(f"non-finite JSON number is not supported: {value}")
|
|
183
|
+
|
|
184
|
+
decoded = json.loads(
|
|
185
|
+
raw_bytes,
|
|
186
|
+
parse_float=Decimal,
|
|
187
|
+
parse_constant=reject_non_finite,
|
|
188
|
+
)
|
|
189
|
+
return selected.adapter.validate_python(decoded)
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
REQUEST_MODELS: dict[str, object | None] = {
|
|
193
|
+
operation_id: operation.request_model for operation_id, operation in OPERATIONS.items()
|
|
194
|
+
}
|
|
195
|
+
RESPONSE_MODELS: dict[tuple[str, int, str], object] = {
|
|
196
|
+
(operation_id, response.status_code, media.media_type): media.model
|
|
197
|
+
for operation_id, operation in OPERATIONS.items()
|
|
198
|
+
for response in operation.responses
|
|
199
|
+
for media in response.media_types
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
__all__ = [
|
|
204
|
+
"ContractLookupError",
|
|
205
|
+
"OPERATIONS",
|
|
206
|
+
"REQUEST_MODELS",
|
|
207
|
+
"RESPONSE_MODELS",
|
|
208
|
+
"encode_request",
|
|
209
|
+
"get_operation_spec",
|
|
210
|
+
"parse_response",
|
|
211
|
+
"validate_request",
|
|
212
|
+
]
|