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 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
+ ]