workhold-admin 1.0.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.
- workhold_admin/__init__.py +140 -0
- workhold_admin/admin.py +573 -0
- workhold_admin/async_client.py +1071 -0
- workhold_admin/async_pagination.py +453 -0
- workhold_admin/break_glass.py +291 -0
- workhold_admin/models.py +1516 -0
- workhold_admin/observer.py +250 -0
- workhold_admin/pagination.py +478 -0
- workhold_admin-1.0.0.dist-info/METADATA +10 -0
- workhold_admin-1.0.0.dist-info/RECORD +11 -0
- workhold_admin-1.0.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
"""Bounded lazy sync pagination for Observer/Admin cursor-list operations.
|
|
2
|
+
|
|
3
|
+
Requires an explicit positive ``max_pages`` and/or ``max_items`` ceiling.
|
|
4
|
+
Preserves caller filters and page size on every request, forwards server
|
|
5
|
+
cursors only (never synthesizes), and surfaces the last server cursor plus
|
|
6
|
+
page/item counts. Does not prefetch or retry cursor protocol errors.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Callable, Iterator
|
|
12
|
+
from dataclasses import dataclass, field
|
|
13
|
+
from datetime import datetime
|
|
14
|
+
from typing import Generic, Protocol, TypeVar
|
|
15
|
+
|
|
16
|
+
from workhold_admin.admin import AdminClient
|
|
17
|
+
from workhold_admin.models import (
|
|
18
|
+
PAGE_LIMIT_DEFAULT,
|
|
19
|
+
AttemptPage,
|
|
20
|
+
AuditPage,
|
|
21
|
+
DeadLetterPage,
|
|
22
|
+
QueuePage,
|
|
23
|
+
TaskPage,
|
|
24
|
+
)
|
|
25
|
+
from workhold_admin.observer import ObserverClient
|
|
26
|
+
|
|
27
|
+
P = TypeVar("P", bound="CursorPage")
|
|
28
|
+
T = TypeVar("T")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class CursorPage(Protocol):
|
|
32
|
+
"""Minimal page shape required by bounded iterators."""
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def items(self) -> tuple[object, ...]: ...
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def next_cursor(self) -> str | None: ...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _effective_max_pages(max_pages: int | None, max_items: int | None) -> int | None:
|
|
42
|
+
"""When only ``max_items`` is set, bound page fetches to avoid empty-cursor spin."""
|
|
43
|
+
|
|
44
|
+
if max_pages is not None:
|
|
45
|
+
return max_pages
|
|
46
|
+
if max_items is not None:
|
|
47
|
+
return max_items
|
|
48
|
+
return None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _validate_bounds(*, max_pages: int | None, max_items: int | None) -> None:
|
|
52
|
+
if max_pages is None and max_items is None:
|
|
53
|
+
raise ValueError("at least one of max_pages or max_items is required")
|
|
54
|
+
if max_pages is not None and (
|
|
55
|
+
not isinstance(max_pages, int) or isinstance(max_pages, bool) or max_pages < 1
|
|
56
|
+
):
|
|
57
|
+
raise ValueError("max_pages must be a positive integer")
|
|
58
|
+
if max_items is not None and (
|
|
59
|
+
not isinstance(max_items, int) or isinstance(max_items, bool) or max_items < 1
|
|
60
|
+
):
|
|
61
|
+
raise ValueError("max_items must be a positive integer")
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@dataclass(slots=True)
|
|
65
|
+
class BoundedPageIterator(Generic[P]):
|
|
66
|
+
"""Lazy page iterator that stops at ``max_pages`` and/or ``max_items``."""
|
|
67
|
+
|
|
68
|
+
_fetch: Callable[[str | None], P]
|
|
69
|
+
_max_pages: int | None
|
|
70
|
+
_max_items: int | None
|
|
71
|
+
_cursor: str | None = None
|
|
72
|
+
_pages_fetched: int = 0
|
|
73
|
+
_items_seen: int = 0
|
|
74
|
+
_last_cursor: str | None = None
|
|
75
|
+
_stopped: bool = False
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def last_cursor(self) -> str | None:
|
|
79
|
+
"""Most recent ``next_cursor`` returned by the server (may be ``None``)."""
|
|
80
|
+
|
|
81
|
+
return self._last_cursor
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def page_count(self) -> int:
|
|
85
|
+
return self._pages_fetched
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def item_count(self) -> int:
|
|
89
|
+
return self._items_seen
|
|
90
|
+
|
|
91
|
+
def __iter__(self) -> Iterator[P]:
|
|
92
|
+
return self
|
|
93
|
+
|
|
94
|
+
def __next__(self) -> P:
|
|
95
|
+
if self._stopped:
|
|
96
|
+
raise StopIteration
|
|
97
|
+
if self._max_pages is not None and self._pages_fetched >= self._max_pages:
|
|
98
|
+
self._stopped = True
|
|
99
|
+
raise StopIteration
|
|
100
|
+
if self._max_items is not None and self._items_seen >= self._max_items:
|
|
101
|
+
self._stopped = True
|
|
102
|
+
raise StopIteration
|
|
103
|
+
|
|
104
|
+
page = self._fetch(self._cursor)
|
|
105
|
+
self._pages_fetched += 1
|
|
106
|
+
self._last_cursor = page.next_cursor
|
|
107
|
+
self._items_seen += len(page.items)
|
|
108
|
+
|
|
109
|
+
if page.next_cursor is None:
|
|
110
|
+
self._stopped = True
|
|
111
|
+
else:
|
|
112
|
+
self._cursor = page.next_cursor
|
|
113
|
+
return page
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
@dataclass(slots=True)
|
|
117
|
+
class BoundedItemIterator(Generic[T]):
|
|
118
|
+
"""Lazy item iterator over cursor pages with the same ceilings."""
|
|
119
|
+
|
|
120
|
+
_fetch: Callable[[str | None], CursorPage]
|
|
121
|
+
_max_pages: int | None
|
|
122
|
+
_max_items: int | None
|
|
123
|
+
_cursor: str | None = None
|
|
124
|
+
_pages_fetched: int = 0
|
|
125
|
+
_items_yielded: int = 0
|
|
126
|
+
_last_cursor: str | None = None
|
|
127
|
+
_pending: list[T] = field(default_factory=list)
|
|
128
|
+
_pages_exhausted: bool = False
|
|
129
|
+
_stopped: bool = False
|
|
130
|
+
|
|
131
|
+
@property
|
|
132
|
+
def last_cursor(self) -> str | None:
|
|
133
|
+
return self._last_cursor
|
|
134
|
+
|
|
135
|
+
@property
|
|
136
|
+
def page_count(self) -> int:
|
|
137
|
+
return self._pages_fetched
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def item_count(self) -> int:
|
|
141
|
+
return self._items_yielded
|
|
142
|
+
|
|
143
|
+
def __iter__(self) -> Iterator[T]:
|
|
144
|
+
return self
|
|
145
|
+
|
|
146
|
+
def __next__(self) -> T:
|
|
147
|
+
while True:
|
|
148
|
+
if self._pending:
|
|
149
|
+
item = self._pending.pop(0)
|
|
150
|
+
self._items_yielded += 1
|
|
151
|
+
if self._max_items is not None and self._items_yielded >= self._max_items:
|
|
152
|
+
self._pending.clear()
|
|
153
|
+
self._stopped = True
|
|
154
|
+
return item
|
|
155
|
+
|
|
156
|
+
if self._stopped or self._pages_exhausted:
|
|
157
|
+
self._stopped = True
|
|
158
|
+
raise StopIteration
|
|
159
|
+
if self._max_pages is not None and self._pages_fetched >= self._max_pages:
|
|
160
|
+
self._stopped = True
|
|
161
|
+
raise StopIteration
|
|
162
|
+
if self._max_items is not None and self._items_yielded >= self._max_items:
|
|
163
|
+
self._stopped = True
|
|
164
|
+
raise StopIteration
|
|
165
|
+
|
|
166
|
+
page = self._fetch(self._cursor)
|
|
167
|
+
self._pages_fetched += 1
|
|
168
|
+
self._last_cursor = page.next_cursor
|
|
169
|
+
self._pending = list(page.items) # type: ignore[arg-type]
|
|
170
|
+
if page.next_cursor is None:
|
|
171
|
+
self._pages_exhausted = True
|
|
172
|
+
else:
|
|
173
|
+
self._cursor = page.next_cursor
|
|
174
|
+
if not self._pending:
|
|
175
|
+
continue
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def bounded_page_iterator(
|
|
179
|
+
fetch: Callable[[str | None], P],
|
|
180
|
+
*,
|
|
181
|
+
max_pages: int | None = None,
|
|
182
|
+
max_items: int | None = None,
|
|
183
|
+
cursor: str | None = None,
|
|
184
|
+
) -> BoundedPageIterator[P]:
|
|
185
|
+
_validate_bounds(max_pages=max_pages, max_items=max_items)
|
|
186
|
+
return BoundedPageIterator(
|
|
187
|
+
_fetch=fetch,
|
|
188
|
+
_max_pages=_effective_max_pages(max_pages, max_items),
|
|
189
|
+
_max_items=max_items,
|
|
190
|
+
_cursor=cursor,
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def bounded_item_iterator(
|
|
195
|
+
fetch: Callable[[str | None], CursorPage],
|
|
196
|
+
*,
|
|
197
|
+
max_pages: int | None = None,
|
|
198
|
+
max_items: int | None = None,
|
|
199
|
+
cursor: str | None = None,
|
|
200
|
+
) -> BoundedItemIterator[T]:
|
|
201
|
+
_validate_bounds(max_pages=max_pages, max_items=max_items)
|
|
202
|
+
return BoundedItemIterator(
|
|
203
|
+
_fetch=fetch,
|
|
204
|
+
_max_pages=_effective_max_pages(max_pages, max_items),
|
|
205
|
+
_max_items=max_items,
|
|
206
|
+
_cursor=cursor,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
def iter_task_attempt_pages(
|
|
211
|
+
client: ObserverClient,
|
|
212
|
+
task_id: str,
|
|
213
|
+
*,
|
|
214
|
+
max_pages: int | None = None,
|
|
215
|
+
max_items: int | None = None,
|
|
216
|
+
limit: int = PAGE_LIMIT_DEFAULT,
|
|
217
|
+
cursor: str | None = None,
|
|
218
|
+
) -> BoundedPageIterator[AttemptPage]:
|
|
219
|
+
return bounded_page_iterator(
|
|
220
|
+
lambda cur: client.list_task_attempts(task_id, cursor=cur, limit=limit),
|
|
221
|
+
max_pages=max_pages,
|
|
222
|
+
max_items=max_items,
|
|
223
|
+
cursor=cursor,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
def iter_task_attempts(
|
|
228
|
+
client: ObserverClient,
|
|
229
|
+
task_id: str,
|
|
230
|
+
*,
|
|
231
|
+
max_pages: int | None = None,
|
|
232
|
+
max_items: int | None = None,
|
|
233
|
+
limit: int = PAGE_LIMIT_DEFAULT,
|
|
234
|
+
cursor: str | None = None,
|
|
235
|
+
) -> BoundedItemIterator[object]:
|
|
236
|
+
return bounded_item_iterator(
|
|
237
|
+
lambda cur: client.list_task_attempts(task_id, cursor=cur, limit=limit),
|
|
238
|
+
max_pages=max_pages,
|
|
239
|
+
max_items=max_items,
|
|
240
|
+
cursor=cursor,
|
|
241
|
+
)
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def iter_inspection_task_pages(
|
|
245
|
+
client: ObserverClient,
|
|
246
|
+
queue_name: str,
|
|
247
|
+
*,
|
|
248
|
+
max_pages: int | None = None,
|
|
249
|
+
max_items: int | None = None,
|
|
250
|
+
limit: int = PAGE_LIMIT_DEFAULT,
|
|
251
|
+
cursor: str | None = None,
|
|
252
|
+
) -> BoundedPageIterator[TaskPage]:
|
|
253
|
+
return bounded_page_iterator(
|
|
254
|
+
lambda cur: client.list_inspection_tasks(queue_name, cursor=cur, limit=limit),
|
|
255
|
+
max_pages=max_pages,
|
|
256
|
+
max_items=max_items,
|
|
257
|
+
cursor=cursor,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
|
|
261
|
+
def iter_inspection_tasks(
|
|
262
|
+
client: ObserverClient,
|
|
263
|
+
queue_name: str,
|
|
264
|
+
*,
|
|
265
|
+
max_pages: int | None = None,
|
|
266
|
+
max_items: int | None = None,
|
|
267
|
+
limit: int = PAGE_LIMIT_DEFAULT,
|
|
268
|
+
cursor: str | None = None,
|
|
269
|
+
) -> BoundedItemIterator[object]:
|
|
270
|
+
return bounded_item_iterator(
|
|
271
|
+
lambda cur: client.list_inspection_tasks(queue_name, cursor=cur, limit=limit),
|
|
272
|
+
max_pages=max_pages,
|
|
273
|
+
max_items=max_items,
|
|
274
|
+
cursor=cursor,
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
def iter_inspection_attempt_pages(
|
|
279
|
+
client: ObserverClient,
|
|
280
|
+
task_id: str,
|
|
281
|
+
*,
|
|
282
|
+
time_from: datetime,
|
|
283
|
+
time_to: datetime,
|
|
284
|
+
max_pages: int | None = None,
|
|
285
|
+
max_items: int | None = None,
|
|
286
|
+
limit: int = PAGE_LIMIT_DEFAULT,
|
|
287
|
+
cursor: str | None = None,
|
|
288
|
+
) -> BoundedPageIterator[AttemptPage]:
|
|
289
|
+
return bounded_page_iterator(
|
|
290
|
+
lambda cur: client.list_inspection_attempts(
|
|
291
|
+
task_id,
|
|
292
|
+
time_from=time_from,
|
|
293
|
+
time_to=time_to,
|
|
294
|
+
cursor=cur,
|
|
295
|
+
limit=limit,
|
|
296
|
+
),
|
|
297
|
+
max_pages=max_pages,
|
|
298
|
+
max_items=max_items,
|
|
299
|
+
cursor=cursor,
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def iter_inspection_attempts(
|
|
304
|
+
client: ObserverClient,
|
|
305
|
+
task_id: str,
|
|
306
|
+
*,
|
|
307
|
+
time_from: datetime,
|
|
308
|
+
time_to: datetime,
|
|
309
|
+
max_pages: int | None = None,
|
|
310
|
+
max_items: int | None = None,
|
|
311
|
+
limit: int = PAGE_LIMIT_DEFAULT,
|
|
312
|
+
cursor: str | None = None,
|
|
313
|
+
) -> BoundedItemIterator[object]:
|
|
314
|
+
return bounded_item_iterator(
|
|
315
|
+
lambda cur: client.list_inspection_attempts(
|
|
316
|
+
task_id,
|
|
317
|
+
time_from=time_from,
|
|
318
|
+
time_to=time_to,
|
|
319
|
+
cursor=cur,
|
|
320
|
+
limit=limit,
|
|
321
|
+
),
|
|
322
|
+
max_pages=max_pages,
|
|
323
|
+
max_items=max_items,
|
|
324
|
+
cursor=cursor,
|
|
325
|
+
)
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def iter_dead_letter_pages(
|
|
329
|
+
client: ObserverClient,
|
|
330
|
+
queue_name: str,
|
|
331
|
+
*,
|
|
332
|
+
time_from: datetime,
|
|
333
|
+
time_to: datetime,
|
|
334
|
+
max_pages: int | None = None,
|
|
335
|
+
max_items: int | None = None,
|
|
336
|
+
limit: int = PAGE_LIMIT_DEFAULT,
|
|
337
|
+
cursor: str | None = None,
|
|
338
|
+
) -> BoundedPageIterator[DeadLetterPage]:
|
|
339
|
+
return bounded_page_iterator(
|
|
340
|
+
lambda cur: client.list_dead_letters(
|
|
341
|
+
queue_name,
|
|
342
|
+
time_from=time_from,
|
|
343
|
+
time_to=time_to,
|
|
344
|
+
cursor=cur,
|
|
345
|
+
limit=limit,
|
|
346
|
+
),
|
|
347
|
+
max_pages=max_pages,
|
|
348
|
+
max_items=max_items,
|
|
349
|
+
cursor=cursor,
|
|
350
|
+
)
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def iter_dead_letters(
|
|
354
|
+
client: ObserverClient,
|
|
355
|
+
queue_name: str,
|
|
356
|
+
*,
|
|
357
|
+
time_from: datetime,
|
|
358
|
+
time_to: datetime,
|
|
359
|
+
max_pages: int | None = None,
|
|
360
|
+
max_items: int | None = None,
|
|
361
|
+
limit: int = PAGE_LIMIT_DEFAULT,
|
|
362
|
+
cursor: str | None = None,
|
|
363
|
+
) -> BoundedItemIterator[object]:
|
|
364
|
+
return bounded_item_iterator(
|
|
365
|
+
lambda cur: client.list_dead_letters(
|
|
366
|
+
queue_name,
|
|
367
|
+
time_from=time_from,
|
|
368
|
+
time_to=time_to,
|
|
369
|
+
cursor=cur,
|
|
370
|
+
limit=limit,
|
|
371
|
+
),
|
|
372
|
+
max_pages=max_pages,
|
|
373
|
+
max_items=max_items,
|
|
374
|
+
cursor=cursor,
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
|
|
378
|
+
def iter_queue_pages(
|
|
379
|
+
client: AdminClient,
|
|
380
|
+
*,
|
|
381
|
+
max_pages: int | None = None,
|
|
382
|
+
max_items: int | None = None,
|
|
383
|
+
limit: int = PAGE_LIMIT_DEFAULT,
|
|
384
|
+
cursor: str | None = None,
|
|
385
|
+
) -> BoundedPageIterator[QueuePage]:
|
|
386
|
+
return bounded_page_iterator(
|
|
387
|
+
lambda cur: client.list_queues(cursor=cur, limit=limit),
|
|
388
|
+
max_pages=max_pages,
|
|
389
|
+
max_items=max_items,
|
|
390
|
+
cursor=cursor,
|
|
391
|
+
)
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def iter_queues(
|
|
395
|
+
client: AdminClient,
|
|
396
|
+
*,
|
|
397
|
+
max_pages: int | None = None,
|
|
398
|
+
max_items: int | None = None,
|
|
399
|
+
limit: int = PAGE_LIMIT_DEFAULT,
|
|
400
|
+
cursor: str | None = None,
|
|
401
|
+
) -> BoundedItemIterator[object]:
|
|
402
|
+
return bounded_item_iterator(
|
|
403
|
+
lambda cur: client.list_queues(cursor=cur, limit=limit),
|
|
404
|
+
max_pages=max_pages,
|
|
405
|
+
max_items=max_items,
|
|
406
|
+
cursor=cursor,
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def iter_admin_audit_pages(
|
|
411
|
+
client: AdminClient,
|
|
412
|
+
*,
|
|
413
|
+
time_from: datetime,
|
|
414
|
+
time_to: datetime,
|
|
415
|
+
queue_name: str | None = None,
|
|
416
|
+
max_pages: int | None = None,
|
|
417
|
+
max_items: int | None = None,
|
|
418
|
+
limit: int = PAGE_LIMIT_DEFAULT,
|
|
419
|
+
cursor: str | None = None,
|
|
420
|
+
) -> BoundedPageIterator[AuditPage]:
|
|
421
|
+
return bounded_page_iterator(
|
|
422
|
+
lambda cur: client.list_admin_audit(
|
|
423
|
+
time_from=time_from,
|
|
424
|
+
time_to=time_to,
|
|
425
|
+
queue_name=queue_name,
|
|
426
|
+
cursor=cur,
|
|
427
|
+
limit=limit,
|
|
428
|
+
),
|
|
429
|
+
max_pages=max_pages,
|
|
430
|
+
max_items=max_items,
|
|
431
|
+
cursor=cursor,
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
def iter_admin_audit(
|
|
436
|
+
client: AdminClient,
|
|
437
|
+
*,
|
|
438
|
+
time_from: datetime,
|
|
439
|
+
time_to: datetime,
|
|
440
|
+
queue_name: str | None = None,
|
|
441
|
+
max_pages: int | None = None,
|
|
442
|
+
max_items: int | None = None,
|
|
443
|
+
limit: int = PAGE_LIMIT_DEFAULT,
|
|
444
|
+
cursor: str | None = None,
|
|
445
|
+
) -> BoundedItemIterator[object]:
|
|
446
|
+
return bounded_item_iterator(
|
|
447
|
+
lambda cur: client.list_admin_audit(
|
|
448
|
+
time_from=time_from,
|
|
449
|
+
time_to=time_to,
|
|
450
|
+
queue_name=queue_name,
|
|
451
|
+
cursor=cur,
|
|
452
|
+
limit=limit,
|
|
453
|
+
),
|
|
454
|
+
max_pages=max_pages,
|
|
455
|
+
max_items=max_items,
|
|
456
|
+
cursor=cursor,
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
__all__ = [
|
|
461
|
+
"BoundedItemIterator",
|
|
462
|
+
"BoundedPageIterator",
|
|
463
|
+
"CursorPage",
|
|
464
|
+
"bounded_item_iterator",
|
|
465
|
+
"bounded_page_iterator",
|
|
466
|
+
"iter_admin_audit",
|
|
467
|
+
"iter_admin_audit_pages",
|
|
468
|
+
"iter_dead_letter_pages",
|
|
469
|
+
"iter_dead_letters",
|
|
470
|
+
"iter_inspection_attempt_pages",
|
|
471
|
+
"iter_inspection_attempts",
|
|
472
|
+
"iter_inspection_task_pages",
|
|
473
|
+
"iter_inspection_tasks",
|
|
474
|
+
"iter_queue_pages",
|
|
475
|
+
"iter_queues",
|
|
476
|
+
"iter_task_attempt_pages",
|
|
477
|
+
"iter_task_attempts",
|
|
478
|
+
]
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: workhold-admin
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Workhold admin/observer/break-glass client (public shell)
|
|
5
|
+
Author: Mikhail Konin
|
|
6
|
+
Author-email: Mikhail Konin <dev.konin@gmail.com>
|
|
7
|
+
Requires-Dist: workhold-client-core>=1.0.0,<1.1.0
|
|
8
|
+
Requires-Dist: workhold-client-core[async]>=1.0.0,<1.1.0 ; extra == 'async'
|
|
9
|
+
Requires-Python: >=3.13
|
|
10
|
+
Provides-Extra: async
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
workhold_admin/__init__.py,sha256=wc9_XjcWaWSiT6-4EzKDQqxgVpfcE17UJtXvD2XTxqQ,3621
|
|
2
|
+
workhold_admin/admin.py,sha256=q25WerW_rHmIHeC2ljJVJDMEx8xW1ySWNFgsORf2RFw,19885
|
|
3
|
+
workhold_admin/async_client.py,sha256=Q2jV50uM6Nrk28Ibf6wjNoS-JpJgVcUbSVgE0J2i9FY,36523
|
|
4
|
+
workhold_admin/async_pagination.py,sha256=8Ex8NlSaDnTSWEBupCC8tHuG7aojTR7sO9LWPo4MMJs,12734
|
|
5
|
+
workhold_admin/break_glass.py,sha256=C2V_nz8z52VbuB27QRFlph3VE__faz3GGR3y4woV2dc,10080
|
|
6
|
+
workhold_admin/models.py,sha256=-LLb9P-0y9ADQlM6FPEbEjXPOc82M1yor3a3l3_V8CM,55039
|
|
7
|
+
workhold_admin/observer.py,sha256=PHNaQJrxR-s5zINo6Gr0nxki24xchXLM_hEHJvFa61w,8602
|
|
8
|
+
workhold_admin/pagination.py,sha256=kNPi2A4DbygLFObcKHvEZXP-wBEcuAVlLRVfkE0EV5M,13279
|
|
9
|
+
workhold_admin-1.0.0.dist-info/WHEEL,sha256=xTCBFIZIIyRSxyowOv5Wqg_jFU6KsdIffuulBB09Ph8,80
|
|
10
|
+
workhold_admin-1.0.0.dist-info/METADATA,sha256=lvCw0P2-Bo00bR6mwrNXlmv5_UbJhfPRUi_R6KhWLSM,369
|
|
11
|
+
workhold_admin-1.0.0.dist-info/RECORD,,
|