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.
@@ -0,0 +1,175 @@
1
+ """Securities and sweep-securities collection operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterator
6
+ from typing import Any, Literal, overload
7
+
8
+ from ..models import Security, SweepSecurity
9
+ from ..odata import DEFAULT_PAGE_SIZE, ODataQuery
10
+ from ..pagination import Page, Paginator
11
+ from ._base import ApiResponseProtocol, HttpInfoMode, Resource
12
+
13
+
14
+ class SecuritiesResource(Resource[HttpInfoMode]):
15
+ OPERATION_IDS = frozenset({"Securities_Query", "Securities_QuerySweep"})
16
+
17
+ def _list_operation(
18
+ self,
19
+ operation_id: str,
20
+ *,
21
+ filter: str | None = None,
22
+ order_by: str | None = None,
23
+ top: int | None = None,
24
+ skip: int | None = None,
25
+ count: bool | None = None,
26
+ ) -> Page[Any] | ApiResponseProtocol[Page[Any]]:
27
+ return self._page(
28
+ operation_id,
29
+ odata=ODataQuery(
30
+ filter=filter,
31
+ order_by=order_by,
32
+ top=top,
33
+ skip=skip,
34
+ count=count,
35
+ ),
36
+ )
37
+
38
+ @overload
39
+ def list(
40
+ self: SecuritiesResource[Literal[False]],
41
+ *,
42
+ filter: str | None = None,
43
+ order_by: str | None = None,
44
+ top: int | None = None,
45
+ skip: int | None = None,
46
+ count: bool | None = None,
47
+ ) -> Page[Security]: ...
48
+
49
+ @overload
50
+ def list(
51
+ self: SecuritiesResource[Literal[True]],
52
+ *,
53
+ filter: str | None = None,
54
+ order_by: str | None = None,
55
+ top: int | None = None,
56
+ skip: int | None = None,
57
+ count: bool | None = None,
58
+ ) -> ApiResponseProtocol[Page[Security]]: ...
59
+
60
+ def list(
61
+ self,
62
+ *,
63
+ filter: str | None = None,
64
+ order_by: str | None = None,
65
+ top: int | None = None,
66
+ skip: int | None = None,
67
+ count: bool | None = None,
68
+ ) -> Page[Security] | ApiResponseProtocol[Page[Security]]:
69
+ return self._list_operation(
70
+ "Securities_Query",
71
+ filter=filter,
72
+ order_by=order_by,
73
+ top=top,
74
+ skip=skip,
75
+ count=count,
76
+ )
77
+
78
+ @overload
79
+ def list_sweeps(
80
+ self: SecuritiesResource[Literal[False]],
81
+ *,
82
+ filter: str | None = None,
83
+ order_by: str | None = None,
84
+ top: int | None = None,
85
+ skip: int | None = None,
86
+ count: bool | None = None,
87
+ ) -> Page[SweepSecurity]: ...
88
+
89
+ @overload
90
+ def list_sweeps(
91
+ self: SecuritiesResource[Literal[True]],
92
+ *,
93
+ filter: str | None = None,
94
+ order_by: str | None = None,
95
+ top: int | None = None,
96
+ skip: int | None = None,
97
+ count: bool | None = None,
98
+ ) -> ApiResponseProtocol[Page[SweepSecurity]]: ...
99
+
100
+ def list_sweeps(
101
+ self,
102
+ *,
103
+ filter: str | None = None,
104
+ order_by: str | None = None,
105
+ top: int | None = None,
106
+ skip: int | None = None,
107
+ count: bool | None = None,
108
+ ) -> Page[SweepSecurity] | ApiResponseProtocol[Page[SweepSecurity]]:
109
+ return self._list_operation(
110
+ "Securities_QuerySweep",
111
+ filter=filter,
112
+ order_by=order_by,
113
+ top=top,
114
+ skip=skip,
115
+ count=count,
116
+ )
117
+
118
+ def _iterator(
119
+ self,
120
+ operation_id: str,
121
+ *,
122
+ filter: str | None,
123
+ order_by: str | None,
124
+ count: bool | None,
125
+ start_skip: int,
126
+ page_size: int,
127
+ max_items: int | None,
128
+ ) -> Paginator[Any]:
129
+ base = ODataQuery(filter=filter, order_by=order_by, count=count, skip=start_skip)
130
+ return Paginator(
131
+ fetch_page=lambda query: self._page_value(operation_id, odata=query),
132
+ query=base,
133
+ page_size=page_size,
134
+ max_items=max_items,
135
+ )
136
+
137
+ def iter_all(
138
+ self,
139
+ *,
140
+ filter: str | None = None,
141
+ order_by: str | None = None,
142
+ count: bool | None = None,
143
+ start_skip: int = 0,
144
+ page_size: int = DEFAULT_PAGE_SIZE,
145
+ max_items: int | None = None,
146
+ ) -> Iterator[Security]:
147
+ return self._iterator(
148
+ "Securities_Query",
149
+ filter=filter,
150
+ order_by=order_by,
151
+ count=count,
152
+ start_skip=start_skip,
153
+ page_size=page_size,
154
+ max_items=max_items,
155
+ ).iter_items()
156
+
157
+ def iter_all_sweeps(
158
+ self,
159
+ *,
160
+ filter: str | None = None,
161
+ order_by: str | None = None,
162
+ count: bool | None = None,
163
+ start_skip: int = 0,
164
+ page_size: int = DEFAULT_PAGE_SIZE,
165
+ max_items: int | None = None,
166
+ ) -> Iterator[SweepSecurity]:
167
+ return self._iterator(
168
+ "Securities_QuerySweep",
169
+ filter=filter,
170
+ order_by=order_by,
171
+ count=count,
172
+ start_skip=start_skip,
173
+ page_size=page_size,
174
+ max_items=max_items,
175
+ ).iter_items()
@@ -0,0 +1,291 @@
1
+ """Task resources, settings lookups, and ETag-protected mutations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterator, Mapping
6
+ from typing import Any, Literal, overload
7
+
8
+ from ..models import (
9
+ Task,
10
+ TaskCreate,
11
+ TaskCreateResult,
12
+ TaskListItem,
13
+ TaskStatus,
14
+ TaskType,
15
+ TaskUpdate,
16
+ TaskUpdateResult,
17
+ )
18
+ from ..odata import DEFAULT_PAGE_SIZE, ODataQuery
19
+ from ..pagination import Page, Paginator
20
+ from ._base import (
21
+ ApiResponseProtocol,
22
+ HttpInfoMode,
23
+ Resource,
24
+ VersionedResource,
25
+ body_value,
26
+ require_etag,
27
+ require_positive_identifier,
28
+ response_header,
29
+ )
30
+
31
+
32
+ class TasksResource(Resource[HttpInfoMode]):
33
+ OPERATION_IDS = frozenset(
34
+ {
35
+ "TaskSettings_ListTypes",
36
+ "TaskSettings_ListStatuses",
37
+ "Tasks_ListTasks",
38
+ "Tasks_Add",
39
+ "Tasks_Get",
40
+ "Tasks_Delete",
41
+ "Tasks_Update",
42
+ }
43
+ )
44
+
45
+ def _collection(
46
+ self,
47
+ operation_id: str,
48
+ *,
49
+ filter: str | None = None,
50
+ order_by: str | None = None,
51
+ top: int | None = None,
52
+ skip: int | None = None,
53
+ count: bool | None = None,
54
+ ) -> Page[Any] | ApiResponseProtocol[Page[Any]]:
55
+ return self._page(
56
+ operation_id,
57
+ odata=ODataQuery(
58
+ filter=filter,
59
+ order_by=order_by,
60
+ top=top,
61
+ skip=skip,
62
+ count=count,
63
+ ),
64
+ )
65
+
66
+ @overload
67
+ def list(
68
+ self: TasksResource[Literal[False]],
69
+ *,
70
+ filter: str | None = None,
71
+ order_by: str | None = None,
72
+ top: int | None = None,
73
+ skip: int | None = None,
74
+ count: bool | None = None,
75
+ ) -> Page[TaskListItem]: ...
76
+
77
+ @overload
78
+ def list(
79
+ self: TasksResource[Literal[True]],
80
+ *,
81
+ filter: str | None = None,
82
+ order_by: str | None = None,
83
+ top: int | None = None,
84
+ skip: int | None = None,
85
+ count: bool | None = None,
86
+ ) -> ApiResponseProtocol[Page[TaskListItem]]: ...
87
+
88
+ def list(
89
+ self,
90
+ *,
91
+ filter: str | None = None,
92
+ order_by: str | None = None,
93
+ top: int | None = None,
94
+ skip: int | None = None,
95
+ count: bool | None = None,
96
+ ) -> Page[TaskListItem] | ApiResponseProtocol[Page[TaskListItem]]:
97
+ return self._collection(
98
+ "Tasks_ListTasks",
99
+ filter=filter,
100
+ order_by=order_by,
101
+ top=top,
102
+ skip=skip,
103
+ count=count,
104
+ )
105
+
106
+ @overload
107
+ def list_types(
108
+ self: TasksResource[Literal[False]],
109
+ *,
110
+ filter: str | None = None,
111
+ order_by: str | None = None,
112
+ top: int | None = None,
113
+ skip: int | None = None,
114
+ count: bool | None = None,
115
+ ) -> Page[TaskType]: ...
116
+
117
+ @overload
118
+ def list_types(
119
+ self: TasksResource[Literal[True]],
120
+ *,
121
+ filter: str | None = None,
122
+ order_by: str | None = None,
123
+ top: int | None = None,
124
+ skip: int | None = None,
125
+ count: bool | None = None,
126
+ ) -> ApiResponseProtocol[Page[TaskType]]: ...
127
+
128
+ def list_types(
129
+ self,
130
+ *,
131
+ filter: str | None = None,
132
+ order_by: str | None = None,
133
+ top: int | None = None,
134
+ skip: int | None = None,
135
+ count: bool | None = None,
136
+ ) -> Page[TaskType] | ApiResponseProtocol[Page[TaskType]]:
137
+ return self._collection(
138
+ "TaskSettings_ListTypes",
139
+ filter=filter,
140
+ order_by=order_by,
141
+ top=top,
142
+ skip=skip,
143
+ count=count,
144
+ )
145
+
146
+ @overload
147
+ def list_statuses(
148
+ self: TasksResource[Literal[False]],
149
+ *,
150
+ filter: str | None = None,
151
+ order_by: str | None = None,
152
+ top: int | None = None,
153
+ skip: int | None = None,
154
+ count: bool | None = None,
155
+ ) -> Page[TaskStatus]: ...
156
+
157
+ @overload
158
+ def list_statuses(
159
+ self: TasksResource[Literal[True]],
160
+ *,
161
+ filter: str | None = None,
162
+ order_by: str | None = None,
163
+ top: int | None = None,
164
+ skip: int | None = None,
165
+ count: bool | None = None,
166
+ ) -> ApiResponseProtocol[Page[TaskStatus]]: ...
167
+
168
+ def list_statuses(
169
+ self,
170
+ *,
171
+ filter: str | None = None,
172
+ order_by: str | None = None,
173
+ top: int | None = None,
174
+ skip: int | None = None,
175
+ count: bool | None = None,
176
+ ) -> Page[TaskStatus] | ApiResponseProtocol[Page[TaskStatus]]:
177
+ return self._collection(
178
+ "TaskSettings_ListStatuses",
179
+ filter=filter,
180
+ order_by=order_by,
181
+ top=top,
182
+ skip=skip,
183
+ count=count,
184
+ )
185
+
186
+ def iter_all(
187
+ self,
188
+ *,
189
+ filter: str | None = None,
190
+ order_by: str | None = None,
191
+ count: bool | None = None,
192
+ start_skip: int = 0,
193
+ page_size: int = DEFAULT_PAGE_SIZE,
194
+ max_items: int | None = None,
195
+ ) -> Iterator[TaskListItem]:
196
+ base = ODataQuery(filter=filter, order_by=order_by, count=count, skip=start_skip)
197
+ return Paginator(
198
+ fetch_page=lambda query: self._page_value("Tasks_ListTasks", odata=query),
199
+ query=base,
200
+ page_size=page_size,
201
+ max_items=max_items,
202
+ ).iter_items()
203
+
204
+ @overload
205
+ def create(
206
+ self: TasksResource[Literal[False]], task: TaskCreate | Mapping[str, object]
207
+ ) -> TaskCreateResult: ...
208
+
209
+ @overload
210
+ def create(
211
+ self: TasksResource[Literal[True]], task: TaskCreate | Mapping[str, object]
212
+ ) -> ApiResponseProtocol[TaskCreateResult]: ...
213
+
214
+ def create(
215
+ self, task: TaskCreate | Mapping[str, object]
216
+ ) -> TaskCreateResult | ApiResponseProtocol[TaskCreateResult]:
217
+ response = self._request("Tasks_Add", json=body_value(task))
218
+ return self._finish(response, response.data)
219
+
220
+ @overload
221
+ def get(self: TasksResource[Literal[False]], task_id: int) -> VersionedResource[Task]: ...
222
+
223
+ @overload
224
+ def get(
225
+ self: TasksResource[Literal[True]], task_id: int
226
+ ) -> ApiResponseProtocol[VersionedResource[Task]]: ...
227
+
228
+ def get(
229
+ self, task_id: int
230
+ ) -> VersionedResource[Task] | ApiResponseProtocol[VersionedResource[Task]]:
231
+ require_positive_identifier("task_id", task_id)
232
+ response = self._request("Tasks_Get", path_params={"id": task_id})
233
+ versioned = VersionedResource(
234
+ data=response.data,
235
+ etag=response_header(response, "ETag"),
236
+ http=response.http,
237
+ )
238
+ return self._finish(response, versioned)
239
+
240
+ @overload
241
+ def update(
242
+ self: TasksResource[Literal[False]],
243
+ task_id: int,
244
+ patch: TaskUpdate | Mapping[str, object],
245
+ *,
246
+ if_match: str,
247
+ ) -> TaskUpdateResult: ...
248
+
249
+ @overload
250
+ def update(
251
+ self: TasksResource[Literal[True]],
252
+ task_id: int,
253
+ patch: TaskUpdate | Mapping[str, object],
254
+ *,
255
+ if_match: str,
256
+ ) -> ApiResponseProtocol[TaskUpdateResult]: ...
257
+
258
+ def update(
259
+ self,
260
+ task_id: int,
261
+ patch: TaskUpdate | Mapping[str, object],
262
+ *,
263
+ if_match: str,
264
+ ) -> TaskUpdateResult | ApiResponseProtocol[TaskUpdateResult]:
265
+ require_positive_identifier("task_id", task_id)
266
+ require_etag(if_match)
267
+ response = self._request(
268
+ "Tasks_Update",
269
+ path_params={"id": task_id},
270
+ headers={"If-Match": if_match},
271
+ json=body_value(patch),
272
+ )
273
+ return self._finish(response, response.data)
274
+
275
+ @overload
276
+ def delete(self: TasksResource[Literal[False]], task_id: int, *, if_match: str) -> None: ...
277
+
278
+ @overload
279
+ def delete(
280
+ self: TasksResource[Literal[True]], task_id: int, *, if_match: str
281
+ ) -> ApiResponseProtocol[None]: ...
282
+
283
+ def delete(self, task_id: int, *, if_match: str) -> None | ApiResponseProtocol[None]:
284
+ require_positive_identifier("task_id", task_id)
285
+ require_etag(if_match)
286
+ response = self._request(
287
+ "Tasks_Delete",
288
+ path_params={"id": task_id},
289
+ headers={"If-Match": if_match},
290
+ )
291
+ return self._finish(response, response.data)
@@ -0,0 +1,88 @@
1
+ """API users collection operations."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterator
6
+ from typing import Literal, overload
7
+
8
+ from ..models import User
9
+ from ..odata import DEFAULT_PAGE_SIZE
10
+ from ..pagination import Page
11
+ from ._base import ApiResponseProtocol, HttpInfoMode
12
+ from ._collections import CollectionResource
13
+
14
+
15
+ class UsersResource(CollectionResource[HttpInfoMode]):
16
+ LIST_OPERATION_ID = "Users_ListUsers"
17
+ OPERATION_IDS = frozenset({LIST_OPERATION_ID})
18
+
19
+ @overload
20
+ def list(
21
+ self: UsersResource[Literal[False]],
22
+ *,
23
+ filter: str | None = None,
24
+ order_by: str | None = None,
25
+ top: int | None = None,
26
+ skip: int | None = None,
27
+ count: bool | None = None,
28
+ ) -> Page[User]: ...
29
+
30
+ @overload
31
+ def list(
32
+ self: UsersResource[Literal[True]],
33
+ *,
34
+ filter: str | None = None,
35
+ order_by: str | None = None,
36
+ top: int | None = None,
37
+ skip: int | None = None,
38
+ count: bool | None = None,
39
+ ) -> ApiResponseProtocol[Page[User]]: ...
40
+
41
+ def list(
42
+ self,
43
+ *,
44
+ filter: str | None = None,
45
+ order_by: str | None = None,
46
+ top: int | None = None,
47
+ skip: int | None = None,
48
+ count: bool | None = None,
49
+ ) -> Page[User] | ApiResponseProtocol[Page[User]]:
50
+ return self._list(filter=filter, order_by=order_by, top=top, skip=skip, count=count)
51
+
52
+ def iter_pages(
53
+ self,
54
+ *,
55
+ filter: str | None = None,
56
+ order_by: str | None = None,
57
+ count: bool | None = None,
58
+ start_skip: int = 0,
59
+ page_size: int = DEFAULT_PAGE_SIZE,
60
+ max_items: int | None = None,
61
+ ) -> Iterator[Page[User]]:
62
+ return self._iter_pages(
63
+ filter=filter,
64
+ order_by=order_by,
65
+ count=count,
66
+ start_skip=start_skip,
67
+ page_size=page_size,
68
+ max_items=max_items,
69
+ )
70
+
71
+ def iter_all(
72
+ self,
73
+ *,
74
+ filter: str | None = None,
75
+ order_by: str | None = None,
76
+ count: bool | None = None,
77
+ start_skip: int = 0,
78
+ page_size: int = DEFAULT_PAGE_SIZE,
79
+ max_items: int | None = None,
80
+ ) -> Iterator[User]:
81
+ return self._iter_all(
82
+ filter=filter,
83
+ order_by=order_by,
84
+ count=count,
85
+ start_skip=start_skip,
86
+ page_size=page_size,
87
+ max_items=max_items,
88
+ )
redblack/response.py ADDED
@@ -0,0 +1,118 @@
1
+ """HTTP metadata containers shared by typed and raw SDK responses."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from collections.abc import Iterator, Mapping
6
+ from dataclasses import dataclass, field
7
+ from typing import Generic, TypeVar
8
+
9
+ from .errors import sanitize_url
10
+
11
+ T = TypeVar("T")
12
+ U = TypeVar("U")
13
+
14
+
15
+ class ResponseHeaders(Mapping[str, str]):
16
+ """An immutable, case-insensitive mapping of safe response headers."""
17
+
18
+ __slots__ = ("_values",)
19
+
20
+ def __init__(self, values: Mapping[str, str] | None = None) -> None:
21
+ self._values = {str(name).lower(): str(value) for name, value in (values or {}).items()}
22
+
23
+ def __getitem__(self, name: str) -> str:
24
+ return self._values[name.lower()]
25
+
26
+ def __iter__(self) -> Iterator[str]:
27
+ return iter(self._values)
28
+
29
+ def __len__(self) -> int:
30
+ return len(self._values)
31
+
32
+ def __repr__(self) -> str:
33
+ return f"ResponseHeaders({self._values!r})"
34
+
35
+
36
+ @dataclass(frozen=True, slots=True, repr=False)
37
+ class HttpMetadata:
38
+ """Non-wire metadata associated with a decoded response.
39
+
40
+ ``location`` may contain a short-lived signed URL and is intentionally
41
+ omitted from representations. ``headers`` contains a sanitized Location
42
+ value, while this dedicated property retains the value for an explicitly
43
+ requested export workflow.
44
+ """
45
+
46
+ status_code: int
47
+ method: str
48
+ url: str
49
+ headers: Mapping[str, str] = field(default_factory=ResponseHeaders)
50
+ request_id: str | None = None
51
+ etag: str | None = None
52
+ location: str | None = field(default=None, repr=False)
53
+ attempts: int = 1
54
+ elapsed: float | None = None
55
+
56
+ def __post_init__(self) -> None:
57
+ object.__setattr__(self, "method", self.method.upper())
58
+ object.__setattr__(self, "url", sanitize_url(self.url))
59
+ if not isinstance(self.headers, ResponseHeaders):
60
+ object.__setattr__(self, "headers", ResponseHeaders(self.headers))
61
+ if self.attempts < 1:
62
+ raise ValueError("attempts must be at least 1")
63
+
64
+ def __repr__(self) -> str:
65
+ return (
66
+ "HttpMetadata("
67
+ f"status_code={self.status_code!r}, method={self.method!r}, "
68
+ "url=<redacted>, "
69
+ f"headers={self.headers!r}, "
70
+ f"request_id={self.request_id!r}, etag={self.etag!r}, "
71
+ f"has_location={self.location is not None}, attempts={self.attempts!r}, "
72
+ f"elapsed={self.elapsed!r})"
73
+ )
74
+
75
+
76
+ @dataclass(frozen=True, slots=True)
77
+ class ApiResponse(Generic[T]):
78
+ """A decoded value plus safe transport details."""
79
+
80
+ data: T
81
+ http: HttpMetadata
82
+ raw: bytes = field(default=b"", repr=False)
83
+
84
+ def __post_init__(self) -> None:
85
+ if not self.raw and isinstance(self.data, bytes):
86
+ object.__setattr__(self, "raw", self.data)
87
+
88
+ @property
89
+ def status_code(self) -> int:
90
+ return self.http.status_code
91
+
92
+ @property
93
+ def headers(self) -> Mapping[str, str]:
94
+ return self.http.headers
95
+
96
+ @property
97
+ def url(self) -> str:
98
+ return self.http.url
99
+
100
+ @property
101
+ def location(self) -> str | None:
102
+ return self.http.location
103
+
104
+ @property
105
+ def request_id(self) -> str | None:
106
+ return self.http.request_id
107
+
108
+ @property
109
+ def etag(self) -> str | None:
110
+ return self.http.etag
111
+
112
+ def with_data(self, value: U) -> ApiResponse[U]:
113
+ """Return the same HTTP response information with a decoded value."""
114
+
115
+ return ApiResponse(data=value, http=self.http, raw=self.raw)
116
+
117
+
118
+ __all__ = ["ApiResponse", "HttpMetadata", "ResponseHeaders"]