b24api 2.1.0__tar.gz → 2.2.0__tar.gz
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.
- {b24api-2.1.0/b24api.egg-info → b24api-2.2.0}/PKG-INFO +58 -17
- {b24api-2.1.0 → b24api-2.2.0}/README.md +57 -16
- {b24api-2.1.0 → b24api-2.2.0}/b24api/__init__.py +32 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/_client_traversal.py +14 -4
- {b24api-2.1.0 → b24api-2.2.0}/b24api/cli.py +35 -2
- {b24api-2.1.0 → b24api-2.2.0}/b24api/cli_contract.py +42 -4
- {b24api-2.1.0 → b24api-2.2.0}/b24api/client.py +68 -1
- {b24api-2.1.0 → b24api-2.2.0}/b24api/contracts/__init__.py +24 -1
- {b24api-2.1.0 → b24api-2.2.0}/b24api/contracts/dispatch.py +7 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/contracts/json.py +1 -1
- b24api-2.2.0/b24api/contracts/keyset_capability.py +246 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/contracts/keyset_execution.py +1 -0
- b24api-2.2.0/b24api/contracts/page.py +70 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/contracts/reference.py +3 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/contracts/report.py +8 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/contracts/response.py +4 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/contracts/traversal.py +6 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/errors.py +109 -1
- {b24api-2.1.0 → b24api-2.2.0}/b24api/execution/context.py +13 -8
- {b24api-2.1.0 → b24api-2.2.0}/b24api/references/binding.py +20 -3
- b24api-2.2.0/b24api/references/dispatch.py +689 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/references/facade.py +9 -9
- {b24api-2.1.0 → b24api-2.2.0}/b24api/references/outcome.py +27 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/references/scheduler.py +99 -5
- {b24api-2.1.0 → b24api-2.2.0}/b24api/references/stream.py +5 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/counted.py +5 -1
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/counted_batch.py +8 -8
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/cursor.py +15 -6
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/driver.py +72 -21
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/facade.py +13 -1
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/identity.py +3 -2
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/keyset.py +5 -4
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/keyset_auto.py +1 -6
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/keyset_capability.py +3 -3
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/keyset_fast_plan.py +4 -4
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/keyset_fast_stream.py +4 -4
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/keyset_observation.py +3 -23
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/keyset_reporting.py +2 -3
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/keyset_scheduler.py +9 -55
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/keyset_transaction_contract.py +4 -4
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/keyset_transactions.py +21 -29
- b24api-2.2.0/b24api/traversal/keyset_verifier.py +498 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/ordered_admission.py +3 -3
- b24api-2.2.0/b24api/traversal/page_adaptation.py +90 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/page_validation.py +26 -6
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/plans.py +7 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/sequential.py +11 -10
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/stream.py +9 -2
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/values.py +54 -36
- {b24api-2.1.0 → b24api-2.2.0/b24api.egg-info}/PKG-INFO +58 -17
- {b24api-2.1.0 → b24api-2.2.0}/b24api.egg-info/SOURCES.txt +4 -0
- b24api-2.1.0/b24api/references/dispatch.py +0 -435
- {b24api-2.1.0 → b24api-2.2.0}/LICENSE +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/MANIFEST.in +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/_audit.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/_error_types.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/_stream.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/batch/__init__.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/batch/engine.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/batch/facade.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/batch/logical.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/batch/outcome.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/batch/stream.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/contracts/command.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/contracts/policy.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/contracts/request.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/contracts/stream.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/contracts/wire.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/encoding.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/execution/__init__.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/execution/cleanup.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/execution/executor.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/execution/failure.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/execution/rate.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/execution/snapshot.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/redaction.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/references/__init__.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/references/fanout.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/references/support.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/settings.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/testing/__init__.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/testing/_isolation.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/testing/transport.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/transport/__init__.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/transport/base.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/transport/httpx.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/transport/protocol.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/__init__.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/keyset_costs.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/keyset_eligibility.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/keyset_partition.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/keyset_range.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api/traversal/keyset_step.py +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api.egg-info/dependency_links.txt +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api.egg-info/entry_points.txt +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api.egg-info/requires.txt +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/b24api.egg-info/top_level.txt +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/pyproject.toml +0 -0
- {b24api-2.1.0 → b24api-2.2.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: b24api
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Bitrix24 API
|
|
5
5
|
Requires-Python: >=3.12
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -139,20 +139,30 @@ more specialized mechanics have explicit names and explicit preconditions.
|
|
|
139
139
|
|
|
140
140
|
| Operation | Use it when | Network mechanics | Completion proof |
|
|
141
141
|
|---|---|---|---|
|
|
142
|
-
| `iter_list` | The method supports ordinary offset pagination. |
|
|
142
|
+
| `iter_list` | The method supports ordinary offset pagination. | Sequential requests follow server `next` (the next offset). Ordinary counted Bitrix list endpoints do server-side COUNT for `total` plus LIMIT/OFFSET page retrieval. | Continuation and empty terminal page; add identity for duplicate detection. |
|
|
143
143
|
| `iter_list_counted` | The first response provides an exact filtered `total` and stable offset pages. | Head page is direct; all known tail offsets are grouped into physical Bitrix batches. | Exact total, ranges and identities. |
|
|
144
|
-
| `iter_list_keyset` | The method may omit `total`, but reliably supports ordering and filtering by a unique integer identity. |
|
|
144
|
+
| `iter_list_keyset` | The method may omit `total`, but reliably supports ordering and filtering by a unique integer identity. | Auto by default: it plans first, then selects boundary-only, sequential, range, or partitioned execution; runtime sends no diagnostic canaries. | Caller-asserted keyset contract, strict monotonic identity, active bound validation, and terminal empty confirmation. |
|
|
145
145
|
| `iter_list_cursor` | Each next request depends on a cursor from the previous response. | Sequential dependent cursor requests. | Strict unique monotonic cursor and empty terminal page. |
|
|
146
|
+
| `iter_cursors` | One or many parent-bound cursor traversals need correlation and shared batching. | Lazy per-parent drivers share the reference batch queue; each binding may have `start_cursor`. | Isolated strict cursor progress and terminal event per binding. |
|
|
146
147
|
| `iter_references` | The same list method must run for many parent parameter sets, such as comments per owner or messages per chat. | Bindings are scheduled with direct or physical-batch dispatch; each binding has its own traversal state. | Per-binding rows, completion/failure and caller correlation. |
|
|
147
148
|
|
|
148
149
|
`page_size` is a local decoded-page cap. It is sent to Bitrix only when you provide the endpoint's
|
|
149
150
|
exact `limit_path`; the client never guesses method-specific parameter names.
|
|
150
151
|
|
|
152
|
+
### List traversal comparison
|
|
153
|
+
|
|
154
|
+

|
|
155
|
+
|
|
151
156
|
### Sequential offset
|
|
152
157
|
|
|
153
158
|
This is the canonical default. It follows the `next` returned by the server and confirms the end
|
|
154
|
-
with an empty page.
|
|
155
|
-
a separate count
|
|
159
|
+
with an empty page. For ordinary counted Bitrix list endpoints, `next` is an offset for the next
|
|
160
|
+
LIMIT/OFFSET page, not a keyset cursor. Producing `total` involves a separate server-side count
|
|
161
|
+
query in addition to retrieving the page. These database operations are performed inside the same
|
|
162
|
+
REST request: the client does not issue an additional HTTP call just for the count. This distinction
|
|
163
|
+
matters for performance: not making a separate HTTP count call does **not** mean avoiding server-side
|
|
164
|
+
COUNT work. `iter_list` does not suppress that work; a returned `total` is observational and does not
|
|
165
|
+
control this strategy's completion. Exact database implementation is endpoint-specific.
|
|
156
166
|
|
|
157
167
|
<!-- tested: tests/client_v2_test.py::test_iter_list_is_sequential_mechanics_only_and_report_is_post_cleanup -->
|
|
158
168
|
```python
|
|
@@ -216,25 +226,50 @@ stream = client.iter_list_counted(
|
|
|
216
226
|
Use it only when `total` is exact for the supplied filter and offset pages are stable. Any missing
|
|
217
227
|
range, overlap, duplicate identity or total contradiction raises `IncompleteTraversalError`.
|
|
218
228
|
|
|
229
|
+
Physical batching reduces HTTP exchanges, but does not suppress server-side COUNT in ordinary
|
|
230
|
+
counted list subrequests. Each command still performs its own offset page retrieval and associated
|
|
231
|
+
total calculation on the server. Do not confuse batching these commands with a no-count traversal.
|
|
232
|
+
|
|
219
233
|
### No-count keyset
|
|
220
234
|
|
|
221
|
-
Keyset traversal
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
before any row is emitted, so partial consumption
|
|
225
|
-
|
|
235
|
+
Keyset traversal uses automatic execution by default. Omitting `execution` is equivalent to
|
|
236
|
+
`AutoKeysetExecution(StableIntegerKeysetContract())`: the client captures both ordered boundaries,
|
|
237
|
+
then selects boundary-only, sequential, range, or partitioned execution from the observed geometry
|
|
238
|
+
and available policy capacity. Planning completes before any row is emitted, so partial consumption
|
|
239
|
+
still pays that barrier cost. A sequential selection made by auto is a cost decision; a failed or
|
|
240
|
+
contradictory fast plan is never silently restarted as sequential.
|
|
241
|
+
|
|
242
|
+
By using the default, the caller asserts that the endpoint has a stable, unique integer key, honors
|
|
243
|
+
strict numeric bounds and ordering, and satisfies empty-confirmation completion. Concurrent mutation
|
|
244
|
+
outside the captured middle is handled by the finishing sweep; mutation inside it is outside this
|
|
245
|
+
assertion. Pass `SequentialKeysetExecution()` explicitly when an endpoint cannot satisfy the fast
|
|
246
|
+
contract or when the previous request-by-request behavior is required.
|
|
247
|
+
|
|
248
|
+
Static incompatibility with the auto contract raises `CapabilityError` from the
|
|
249
|
+
`iter_list_keyset(...)` call before iteration begins. A portal that accepts but contradicts the
|
|
250
|
+
declared controls fails before emission with `IncompleteTraversalError`; auto never restarts that
|
|
251
|
+
operation silently. `KeysetTraversal` inside reference traversal remains sequential-only. The
|
|
252
|
+
terminal report now includes `keyset_execution` for omitted-execution keyset calls so consumers can
|
|
253
|
+
see the requested and selected plan.
|
|
254
|
+
|
|
255
|
+
Use `await client.verify_keyset_capability(...)` as a development/CI/staging guard on a stable
|
|
256
|
+
representative fixture. It performs five strict-bound checks and returns only a `VERIFIED` report;
|
|
257
|
+
unsupported and inconclusive verdicts raise `KeysetCapabilityError`. The ordinary
|
|
258
|
+
`iter_list_keyset()` remains a separate caller-asserted operation with zero verifier canaries and
|
|
259
|
+
may emit a partial prefix before a late endpoint contradiction is detected.
|
|
260
|
+
|
|
261
|
+
Every list operation also accepts an immutable `PageAdapter` strategy. The adapter synchronously
|
|
262
|
+
maps selected frozen items using sibling result metadata while preserving cardinality, order and
|
|
263
|
+
configured identities. The identity adapter is the default and preserves existing JSON output.
|
|
226
264
|
|
|
227
|
-
The caller must assert that the endpoint has a stable, unique integer key, honors strict numeric
|
|
228
|
-
bounds and ordering, and satisfies the chosen page-completion rule. Concurrent mutation outside the
|
|
229
|
-
captured middle is handled by the finishing sweep; mutation inside it is outside this assertion.
|
|
230
265
|
An advisory `total` may only raise an automatic cost estimate and never proves completion. Reports
|
|
231
266
|
record the selected strategy and reason: unbounded auto continuation has the same
|
|
232
267
|
`ordered_prefix_only` assurance as sequential traversal, while bounded plans additionally report
|
|
233
|
-
`
|
|
268
|
+
`caller_asserted_bounds`. Runtime traversal does not perform verifier canaries.
|
|
234
269
|
|
|
235
|
-
<!-- tested: tests/
|
|
270
|
+
<!-- tested: tests/keyset_fast_test.py::test_omitted_execution_defaults_to_auto -->
|
|
236
271
|
```python
|
|
237
|
-
from b24api import
|
|
272
|
+
from b24api import KeysetSpec, ParameterPath
|
|
238
273
|
|
|
239
274
|
stream = client.iter_list_keyset(
|
|
240
275
|
Request("example.item.list", replay_safety=ReplaySafety.SAFE),
|
|
@@ -244,7 +279,6 @@ stream = client.iter_list_keyset(
|
|
|
244
279
|
filter_path=ParameterPath(("filter",)),
|
|
245
280
|
order_path=ParameterPath(("order",)),
|
|
246
281
|
),
|
|
247
|
-
execution=AutoKeysetExecution(contract=StableIntegerKeysetContract()),
|
|
248
282
|
)
|
|
249
283
|
```
|
|
250
284
|
|
|
@@ -273,6 +307,11 @@ stream = client.iter_list_cursor(
|
|
|
273
307
|
Cursor values must be unique and strictly monotonic. If an endpoint exposes only a non-unique
|
|
274
308
|
boundary, use an application-owned direct-call workflow or supply a unique tie-breaker.
|
|
275
309
|
|
|
310
|
+
For multiple parent-bound cursor chains, `iter_cursors()` keeps cursor progress and correlation
|
|
311
|
+
isolated per binding while ready pages share the physical batch queue.
|
|
312
|
+
|
|
313
|
+

|
|
314
|
+
|
|
276
315
|
See [architecture](docs/architecture.md), [migration](docs/migration.md),
|
|
277
316
|
[performance](docs/performance.md), and [endpoint recipes](docs/recipes.md) for the complete
|
|
278
317
|
contracts and selection guidance.
|
|
@@ -283,6 +322,8 @@ contracts and selection guidance.
|
|
|
283
322
|
client remains unaware of entity types: a binding can represent a deal, lead, chat or any other
|
|
284
323
|
caller-defined parent.
|
|
285
324
|
|
|
325
|
+

|
|
326
|
+
|
|
286
327
|
<!-- tested: tests/client_v2_test.py::test_bound_references_apply_nested_updates_off_wire_and_emit_exact_completion -->
|
|
287
328
|
```python
|
|
288
329
|
from b24api import (
|
|
@@ -127,20 +127,30 @@ more specialized mechanics have explicit names and explicit preconditions.
|
|
|
127
127
|
|
|
128
128
|
| Operation | Use it when | Network mechanics | Completion proof |
|
|
129
129
|
|---|---|---|---|
|
|
130
|
-
| `iter_list` | The method supports ordinary offset pagination. |
|
|
130
|
+
| `iter_list` | The method supports ordinary offset pagination. | Sequential requests follow server `next` (the next offset). Ordinary counted Bitrix list endpoints do server-side COUNT for `total` plus LIMIT/OFFSET page retrieval. | Continuation and empty terminal page; add identity for duplicate detection. |
|
|
131
131
|
| `iter_list_counted` | The first response provides an exact filtered `total` and stable offset pages. | Head page is direct; all known tail offsets are grouped into physical Bitrix batches. | Exact total, ranges and identities. |
|
|
132
|
-
| `iter_list_keyset` | The method may omit `total`, but reliably supports ordering and filtering by a unique integer identity. |
|
|
132
|
+
| `iter_list_keyset` | The method may omit `total`, but reliably supports ordering and filtering by a unique integer identity. | Auto by default: it plans first, then selects boundary-only, sequential, range, or partitioned execution; runtime sends no diagnostic canaries. | Caller-asserted keyset contract, strict monotonic identity, active bound validation, and terminal empty confirmation. |
|
|
133
133
|
| `iter_list_cursor` | Each next request depends on a cursor from the previous response. | Sequential dependent cursor requests. | Strict unique monotonic cursor and empty terminal page. |
|
|
134
|
+
| `iter_cursors` | One or many parent-bound cursor traversals need correlation and shared batching. | Lazy per-parent drivers share the reference batch queue; each binding may have `start_cursor`. | Isolated strict cursor progress and terminal event per binding. |
|
|
134
135
|
| `iter_references` | The same list method must run for many parent parameter sets, such as comments per owner or messages per chat. | Bindings are scheduled with direct or physical-batch dispatch; each binding has its own traversal state. | Per-binding rows, completion/failure and caller correlation. |
|
|
135
136
|
|
|
136
137
|
`page_size` is a local decoded-page cap. It is sent to Bitrix only when you provide the endpoint's
|
|
137
138
|
exact `limit_path`; the client never guesses method-specific parameter names.
|
|
138
139
|
|
|
140
|
+
### List traversal comparison
|
|
141
|
+
|
|
142
|
+

|
|
143
|
+
|
|
139
144
|
### Sequential offset
|
|
140
145
|
|
|
141
146
|
This is the canonical default. It follows the `next` returned by the server and confirms the end
|
|
142
|
-
with an empty page.
|
|
143
|
-
a separate count
|
|
147
|
+
with an empty page. For ordinary counted Bitrix list endpoints, `next` is an offset for the next
|
|
148
|
+
LIMIT/OFFSET page, not a keyset cursor. Producing `total` involves a separate server-side count
|
|
149
|
+
query in addition to retrieving the page. These database operations are performed inside the same
|
|
150
|
+
REST request: the client does not issue an additional HTTP call just for the count. This distinction
|
|
151
|
+
matters for performance: not making a separate HTTP count call does **not** mean avoiding server-side
|
|
152
|
+
COUNT work. `iter_list` does not suppress that work; a returned `total` is observational and does not
|
|
153
|
+
control this strategy's completion. Exact database implementation is endpoint-specific.
|
|
144
154
|
|
|
145
155
|
<!-- tested: tests/client_v2_test.py::test_iter_list_is_sequential_mechanics_only_and_report_is_post_cleanup -->
|
|
146
156
|
```python
|
|
@@ -204,25 +214,50 @@ stream = client.iter_list_counted(
|
|
|
204
214
|
Use it only when `total` is exact for the supplied filter and offset pages are stable. Any missing
|
|
205
215
|
range, overlap, duplicate identity or total contradiction raises `IncompleteTraversalError`.
|
|
206
216
|
|
|
217
|
+
Physical batching reduces HTTP exchanges, but does not suppress server-side COUNT in ordinary
|
|
218
|
+
counted list subrequests. Each command still performs its own offset page retrieval and associated
|
|
219
|
+
total calculation on the server. Do not confuse batching these commands with a no-count traversal.
|
|
220
|
+
|
|
207
221
|
### No-count keyset
|
|
208
222
|
|
|
209
|
-
Keyset traversal
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
before any row is emitted, so partial consumption
|
|
213
|
-
|
|
223
|
+
Keyset traversal uses automatic execution by default. Omitting `execution` is equivalent to
|
|
224
|
+
`AutoKeysetExecution(StableIntegerKeysetContract())`: the client captures both ordered boundaries,
|
|
225
|
+
then selects boundary-only, sequential, range, or partitioned execution from the observed geometry
|
|
226
|
+
and available policy capacity. Planning completes before any row is emitted, so partial consumption
|
|
227
|
+
still pays that barrier cost. A sequential selection made by auto is a cost decision; a failed or
|
|
228
|
+
contradictory fast plan is never silently restarted as sequential.
|
|
229
|
+
|
|
230
|
+
By using the default, the caller asserts that the endpoint has a stable, unique integer key, honors
|
|
231
|
+
strict numeric bounds and ordering, and satisfies empty-confirmation completion. Concurrent mutation
|
|
232
|
+
outside the captured middle is handled by the finishing sweep; mutation inside it is outside this
|
|
233
|
+
assertion. Pass `SequentialKeysetExecution()` explicitly when an endpoint cannot satisfy the fast
|
|
234
|
+
contract or when the previous request-by-request behavior is required.
|
|
235
|
+
|
|
236
|
+
Static incompatibility with the auto contract raises `CapabilityError` from the
|
|
237
|
+
`iter_list_keyset(...)` call before iteration begins. A portal that accepts but contradicts the
|
|
238
|
+
declared controls fails before emission with `IncompleteTraversalError`; auto never restarts that
|
|
239
|
+
operation silently. `KeysetTraversal` inside reference traversal remains sequential-only. The
|
|
240
|
+
terminal report now includes `keyset_execution` for omitted-execution keyset calls so consumers can
|
|
241
|
+
see the requested and selected plan.
|
|
242
|
+
|
|
243
|
+
Use `await client.verify_keyset_capability(...)` as a development/CI/staging guard on a stable
|
|
244
|
+
representative fixture. It performs five strict-bound checks and returns only a `VERIFIED` report;
|
|
245
|
+
unsupported and inconclusive verdicts raise `KeysetCapabilityError`. The ordinary
|
|
246
|
+
`iter_list_keyset()` remains a separate caller-asserted operation with zero verifier canaries and
|
|
247
|
+
may emit a partial prefix before a late endpoint contradiction is detected.
|
|
248
|
+
|
|
249
|
+
Every list operation also accepts an immutable `PageAdapter` strategy. The adapter synchronously
|
|
250
|
+
maps selected frozen items using sibling result metadata while preserving cardinality, order and
|
|
251
|
+
configured identities. The identity adapter is the default and preserves existing JSON output.
|
|
214
252
|
|
|
215
|
-
The caller must assert that the endpoint has a stable, unique integer key, honors strict numeric
|
|
216
|
-
bounds and ordering, and satisfies the chosen page-completion rule. Concurrent mutation outside the
|
|
217
|
-
captured middle is handled by the finishing sweep; mutation inside it is outside this assertion.
|
|
218
253
|
An advisory `total` may only raise an automatic cost estimate and never proves completion. Reports
|
|
219
254
|
record the selected strategy and reason: unbounded auto continuation has the same
|
|
220
255
|
`ordered_prefix_only` assurance as sequential traversal, while bounded plans additionally report
|
|
221
|
-
`
|
|
256
|
+
`caller_asserted_bounds`. Runtime traversal does not perform verifier canaries.
|
|
222
257
|
|
|
223
|
-
<!-- tested: tests/
|
|
258
|
+
<!-- tested: tests/keyset_fast_test.py::test_omitted_execution_defaults_to_auto -->
|
|
224
259
|
```python
|
|
225
|
-
from b24api import
|
|
260
|
+
from b24api import KeysetSpec, ParameterPath
|
|
226
261
|
|
|
227
262
|
stream = client.iter_list_keyset(
|
|
228
263
|
Request("example.item.list", replay_safety=ReplaySafety.SAFE),
|
|
@@ -232,7 +267,6 @@ stream = client.iter_list_keyset(
|
|
|
232
267
|
filter_path=ParameterPath(("filter",)),
|
|
233
268
|
order_path=ParameterPath(("order",)),
|
|
234
269
|
),
|
|
235
|
-
execution=AutoKeysetExecution(contract=StableIntegerKeysetContract()),
|
|
236
270
|
)
|
|
237
271
|
```
|
|
238
272
|
|
|
@@ -261,6 +295,11 @@ stream = client.iter_list_cursor(
|
|
|
261
295
|
Cursor values must be unique and strictly monotonic. If an endpoint exposes only a non-unique
|
|
262
296
|
boundary, use an application-owned direct-call workflow or supply a unique tie-breaker.
|
|
263
297
|
|
|
298
|
+
For multiple parent-bound cursor chains, `iter_cursors()` keeps cursor progress and correlation
|
|
299
|
+
isolated per binding while ready pages share the physical batch queue.
|
|
300
|
+
|
|
301
|
+

|
|
302
|
+
|
|
264
303
|
See [architecture](docs/architecture.md), [migration](docs/migration.md),
|
|
265
304
|
[performance](docs/performance.md), and [endpoint recipes](docs/recipes.md) for the complete
|
|
266
305
|
contracts and selection guidance.
|
|
@@ -271,6 +310,8 @@ contracts and selection guidance.
|
|
|
271
310
|
client remains unaware of entity types: a binding can represent a deal, lead, chat or any other
|
|
272
311
|
caller-defined parent.
|
|
273
312
|
|
|
313
|
+

|
|
314
|
+
|
|
274
315
|
<!-- tested: tests/client_v2_test.py::test_bound_references_apply_nested_updates_off_wire_and_emit_exact_completion -->
|
|
275
316
|
```python
|
|
276
317
|
from b24api import (
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from b24api.client import Bitrix24
|
|
4
4
|
from b24api.contracts import (
|
|
5
|
+
AdaptedPage,
|
|
5
6
|
AmbiguityPolicy,
|
|
6
7
|
AmbiguityReason,
|
|
7
8
|
AutoKeysetExecution,
|
|
@@ -25,27 +26,39 @@ from b24api.contracts import (
|
|
|
25
26
|
DeliveryOrder,
|
|
26
27
|
DirectDispatch,
|
|
27
28
|
ExecutionPolicy,
|
|
29
|
+
FrozenJson,
|
|
30
|
+
FrozenMapping,
|
|
28
31
|
IdentityCoercion,
|
|
29
32
|
IdentityComponent,
|
|
33
|
+
IdentityPageAdapter,
|
|
30
34
|
IdentitySpec,
|
|
31
35
|
KeysetAssuranceSource,
|
|
36
|
+
KeysetCapabilityCheckName,
|
|
37
|
+
KeysetCapabilityCheckOutcome,
|
|
38
|
+
KeysetCapabilityCheckResult,
|
|
39
|
+
KeysetCapabilityReport,
|
|
40
|
+
KeysetCapabilityVerdict,
|
|
32
41
|
KeysetExecution,
|
|
33
42
|
KeysetExecutionKind,
|
|
34
43
|
KeysetExecutionReport,
|
|
44
|
+
KeysetInconclusiveReason,
|
|
35
45
|
KeysetPageCompletion,
|
|
36
46
|
KeysetPhase,
|
|
37
47
|
KeysetSelectionReason,
|
|
38
48
|
KeysetSpec,
|
|
39
49
|
KeysetTraversal,
|
|
50
|
+
MembershipRecheck,
|
|
40
51
|
NotExecutedReason,
|
|
41
52
|
OffsetContinuation,
|
|
42
53
|
OffsetSpec,
|
|
43
54
|
OperationReport,
|
|
44
55
|
OperationStream,
|
|
56
|
+
PageAdapter,
|
|
45
57
|
PageDispatch,
|
|
46
58
|
PageOutcome,
|
|
47
59
|
PageRecord,
|
|
48
60
|
PageRejectionCode,
|
|
61
|
+
PageView,
|
|
49
62
|
ParameterPath,
|
|
50
63
|
ParameterUpdate,
|
|
51
64
|
PartialResult,
|
|
@@ -99,6 +112,9 @@ from b24api.errors import (
|
|
|
99
112
|
IdentityContractError,
|
|
100
113
|
IncompleteTraversalError,
|
|
101
114
|
InputSourceError,
|
|
115
|
+
KeysetCapabilityError,
|
|
116
|
+
PageAdaptationError,
|
|
117
|
+
PageAdaptationViolation,
|
|
102
118
|
PaginationError,
|
|
103
119
|
ProtocolError,
|
|
104
120
|
ReferenceFailed,
|
|
@@ -110,6 +126,7 @@ from b24api.settings import Settings
|
|
|
110
126
|
from b24api.transport import Transport, TransportCapabilities, WireRequest, WireResponse, WireTransport
|
|
111
127
|
|
|
112
128
|
__all__ = [
|
|
129
|
+
"AdaptedPage",
|
|
113
130
|
"AmbiguityPolicy",
|
|
114
131
|
"AmbiguityReason",
|
|
115
132
|
"AmbiguousExecutionError",
|
|
@@ -142,31 +159,46 @@ __all__ = [
|
|
|
142
159
|
"DirectDispatch",
|
|
143
160
|
"EnvelopeContractError",
|
|
144
161
|
"ExecutionPolicy",
|
|
162
|
+
"FrozenJson",
|
|
163
|
+
"FrozenMapping",
|
|
145
164
|
"HTTPGatewayError",
|
|
146
165
|
"IdentityCoercion",
|
|
147
166
|
"IdentityComponent",
|
|
148
167
|
"IdentityContractError",
|
|
168
|
+
"IdentityPageAdapter",
|
|
149
169
|
"IdentitySpec",
|
|
150
170
|
"IncompleteTraversalError",
|
|
151
171
|
"InputSourceError",
|
|
152
172
|
"KeysetAssuranceSource",
|
|
173
|
+
"KeysetCapabilityCheckName",
|
|
174
|
+
"KeysetCapabilityCheckOutcome",
|
|
175
|
+
"KeysetCapabilityCheckResult",
|
|
176
|
+
"KeysetCapabilityError",
|
|
177
|
+
"KeysetCapabilityReport",
|
|
178
|
+
"KeysetCapabilityVerdict",
|
|
153
179
|
"KeysetExecution",
|
|
154
180
|
"KeysetExecutionKind",
|
|
155
181
|
"KeysetExecutionReport",
|
|
182
|
+
"KeysetInconclusiveReason",
|
|
156
183
|
"KeysetPageCompletion",
|
|
157
184
|
"KeysetPhase",
|
|
158
185
|
"KeysetSelectionReason",
|
|
159
186
|
"KeysetSpec",
|
|
160
187
|
"KeysetTraversal",
|
|
188
|
+
"MembershipRecheck",
|
|
161
189
|
"NotExecutedReason",
|
|
162
190
|
"OffsetContinuation",
|
|
163
191
|
"OffsetSpec",
|
|
164
192
|
"OperationReport",
|
|
165
193
|
"OperationStream",
|
|
194
|
+
"PageAdaptationError",
|
|
195
|
+
"PageAdaptationViolation",
|
|
196
|
+
"PageAdapter",
|
|
166
197
|
"PageDispatch",
|
|
167
198
|
"PageOutcome",
|
|
168
199
|
"PageRecord",
|
|
169
200
|
"PageRejectionCode",
|
|
201
|
+
"PageView",
|
|
170
202
|
"PaginationError",
|
|
171
203
|
"ParameterPath",
|
|
172
204
|
"ParameterUpdate",
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
from typing import TYPE_CHECKING
|
|
5
5
|
|
|
6
|
-
from b24api.contracts.keyset_execution import KeysetExecution,
|
|
6
|
+
from b24api.contracts.keyset_execution import AutoKeysetExecution, KeysetExecution, StableIntegerKeysetContract
|
|
7
|
+
from b24api.contracts.page import IdentityPageAdapter, PageAdapter
|
|
7
8
|
from b24api.contracts.request import IdentitySpec, RequestLike, ResultSelector, TraversalIdentity, canonical_request
|
|
8
9
|
from b24api.contracts.response import ResultCollectionShape
|
|
9
10
|
from b24api.contracts.traversal import CursorSpec, KeysetSpec, OffsetSpec, TotalTermination
|
|
@@ -21,7 +22,8 @@ _ROOT_SELECTOR = ResultSelector.root()
|
|
|
21
22
|
_DEFAULT_OFFSET = OffsetSpec()
|
|
22
23
|
_DEFAULT_COUNTED_OFFSET = OffsetSpec(total_termination=TotalTermination.EXACT_QUALIFIED)
|
|
23
24
|
_DEFAULT_KEYSET = KeysetSpec()
|
|
24
|
-
|
|
25
|
+
_DEFAULT_AUTO_KEYSET_EXECUTION = AutoKeysetExecution(StableIntegerKeysetContract())
|
|
26
|
+
_IDENTITY_PAGE_ADAPTER = IdentityPageAdapter()
|
|
25
27
|
|
|
26
28
|
|
|
27
29
|
class _TraversalFacade:
|
|
@@ -51,6 +53,7 @@ class _TraversalFacade:
|
|
|
51
53
|
collection_shape: ResultCollectionShape = ResultCollectionShape.SEQUENCE,
|
|
52
54
|
page_size: int = 50,
|
|
53
55
|
offset: OffsetSpec = _DEFAULT_OFFSET,
|
|
56
|
+
page_adapter: PageAdapter = _IDENTITY_PAGE_ADAPTER,
|
|
54
57
|
policy: ExecutionPolicy | None = None,
|
|
55
58
|
) -> OperationStream[JsonValue]:
|
|
56
59
|
"""Return conservative sequential offset/server-next traversal."""
|
|
@@ -66,6 +69,7 @@ class _TraversalFacade:
|
|
|
66
69
|
collection_shape=collection_shape,
|
|
67
70
|
page_size=page_size,
|
|
68
71
|
offset=offset,
|
|
72
|
+
page_adapter=page_adapter,
|
|
69
73
|
policy=policy or self._default_policy,
|
|
70
74
|
deregister=self._discard_stream,
|
|
71
75
|
audit_violations=(() if audit_violation is None else (audit_violation,)),
|
|
@@ -82,6 +86,7 @@ class _TraversalFacade:
|
|
|
82
86
|
page_size: int = 50,
|
|
83
87
|
batch_size: int | None = None,
|
|
84
88
|
offset: OffsetSpec = _DEFAULT_COUNTED_OFFSET,
|
|
89
|
+
page_adapter: PageAdapter = _IDENTITY_PAGE_ADAPTER,
|
|
85
90
|
policy: ExecutionPolicy | None = None,
|
|
86
91
|
) -> OperationStream[JsonValue]:
|
|
87
92
|
"""Return exact direct-head plus physically batched counted traversal."""
|
|
@@ -98,6 +103,7 @@ class _TraversalFacade:
|
|
|
98
103
|
page_size=page_size,
|
|
99
104
|
batch_size=batch_size,
|
|
100
105
|
offset=offset,
|
|
106
|
+
page_adapter=page_adapter,
|
|
101
107
|
policy=policy or self._default_policy,
|
|
102
108
|
deregister=self._discard_stream,
|
|
103
109
|
audit_violations=(() if audit_violation is None else (audit_violation,)),
|
|
@@ -113,10 +119,11 @@ class _TraversalFacade:
|
|
|
113
119
|
collection_shape: ResultCollectionShape = ResultCollectionShape.SEQUENCE,
|
|
114
120
|
page_size: int = 50,
|
|
115
121
|
keyset: KeysetSpec = _DEFAULT_KEYSET,
|
|
116
|
-
execution: KeysetExecution =
|
|
122
|
+
execution: KeysetExecution = _DEFAULT_AUTO_KEYSET_EXECUTION,
|
|
123
|
+
page_adapter: PageAdapter = _IDENTITY_PAGE_ADAPTER,
|
|
117
124
|
policy: ExecutionPolicy | None = None,
|
|
118
125
|
) -> OperationStream[JsonValue]:
|
|
119
|
-
"""Return
|
|
126
|
+
"""Return automatic no-count keyset traversal with explicit execution override."""
|
|
120
127
|
self._require_open()
|
|
121
128
|
canonical = canonical_request(request)
|
|
122
129
|
audit_violation = self._audit_unknown(canonical)
|
|
@@ -130,6 +137,7 @@ class _TraversalFacade:
|
|
|
130
137
|
page_size=page_size,
|
|
131
138
|
keyset=keyset,
|
|
132
139
|
execution=execution,
|
|
140
|
+
page_adapter=page_adapter,
|
|
133
141
|
policy=policy or self._default_policy,
|
|
134
142
|
deregister=self._discard_stream,
|
|
135
143
|
audit_violations=(() if audit_violation is None else (audit_violation,)),
|
|
@@ -145,6 +153,7 @@ class _TraversalFacade:
|
|
|
145
153
|
identity: IdentitySpec | None = None,
|
|
146
154
|
collection_shape: ResultCollectionShape = ResultCollectionShape.SEQUENCE,
|
|
147
155
|
page_size: int = 50,
|
|
156
|
+
page_adapter: PageAdapter = _IDENTITY_PAGE_ADAPTER,
|
|
148
157
|
policy: ExecutionPolicy | None = None,
|
|
149
158
|
) -> OperationStream[JsonValue]:
|
|
150
159
|
"""Return strict dependent cursor traversal with empty confirmation."""
|
|
@@ -160,6 +169,7 @@ class _TraversalFacade:
|
|
|
160
169
|
identity=identity,
|
|
161
170
|
collection_shape=collection_shape,
|
|
162
171
|
page_size=page_size,
|
|
172
|
+
page_adapter=page_adapter,
|
|
163
173
|
policy=policy or self._default_policy,
|
|
164
174
|
deregister=self._discard_stream,
|
|
165
175
|
audit_violations=(() if audit_violation is None else (audit_violation,)),
|
|
@@ -11,6 +11,7 @@ from typing import TYPE_CHECKING, NoReturn, TextIO
|
|
|
11
11
|
from b24api import (
|
|
12
12
|
B24ApiError,
|
|
13
13
|
Bitrix24,
|
|
14
|
+
KeysetCapabilityError,
|
|
14
15
|
ReplaySafety,
|
|
15
16
|
Request,
|
|
16
17
|
Response,
|
|
@@ -20,10 +21,12 @@ from b24api import (
|
|
|
20
21
|
from b24api.cli_contract import (
|
|
21
22
|
CliUsageError,
|
|
22
23
|
ListContractRoute,
|
|
24
|
+
VerifyKeysetContractRoute,
|
|
23
25
|
cli_request,
|
|
24
26
|
default_contract,
|
|
25
27
|
list_stream,
|
|
26
28
|
parse_list_contract,
|
|
29
|
+
parse_verify_keyset_contract,
|
|
27
30
|
read_json_source,
|
|
28
31
|
)
|
|
29
32
|
|
|
@@ -37,6 +40,8 @@ _USAGE = 2
|
|
|
37
40
|
_UNAVAILABLE = 3
|
|
38
41
|
_CORRECTNESS = 4
|
|
39
42
|
_OUTPUT_CLOSED = 5
|
|
43
|
+
_KEYSET_UNSUPPORTED = 6
|
|
44
|
+
_KEYSET_INCONCLUSIVE = 7
|
|
40
45
|
_INTERRUPTED = 130
|
|
41
46
|
|
|
42
47
|
|
|
@@ -119,6 +124,10 @@ def _parser() -> argparse.ArgumentParser:
|
|
|
119
124
|
help="traversal mechanics (default: sequential)",
|
|
120
125
|
)
|
|
121
126
|
listing.add_argument("--contract", help="closed v1 traversal contract as @file or -")
|
|
127
|
+
verify = subparsers.add_parser("verify-keyset", help="verify strict keyset bounds for one portal method")
|
|
128
|
+
verify.add_argument("method", help="Bitrix24 REST method name")
|
|
129
|
+
verify.add_argument("--params", help="JSON object, @file, or - for stdin")
|
|
130
|
+
verify.add_argument("--contract", required=True, help="closed v1 verifier contract as @file")
|
|
122
131
|
return parser
|
|
123
132
|
|
|
124
133
|
|
|
@@ -168,7 +177,20 @@ async def _list(
|
|
|
168
177
|
raise RuntimeError("list traversal did not complete successfully")
|
|
169
178
|
|
|
170
179
|
|
|
171
|
-
def
|
|
180
|
+
async def _verify_keyset(request: Request, route: VerifyKeysetContractRoute, stdout: TextIO) -> None:
|
|
181
|
+
async with Bitrix24() as client:
|
|
182
|
+
report = await client.verify_keyset_capability(
|
|
183
|
+
request,
|
|
184
|
+
selector=route.selector,
|
|
185
|
+
identity=route.identity,
|
|
186
|
+
collection_shape=route.collection_shape,
|
|
187
|
+
page_size=route.page_size,
|
|
188
|
+
keyset=route.keyset,
|
|
189
|
+
)
|
|
190
|
+
_write_json(stdout, report.to_dict())
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def main( # noqa: C901, PLR0911, PLR0912 - stable process-code boundary
|
|
172
194
|
argv: Sequence[str] | None = None,
|
|
173
195
|
*,
|
|
174
196
|
stdin: TextIO | None = None,
|
|
@@ -185,11 +207,16 @@ def main( # noqa: PLR0911 - stable process-code boundary
|
|
|
185
207
|
if args.command == "call":
|
|
186
208
|
request = cli_request(args.method, params, ReplaySafety(args.replay_safety))
|
|
187
209
|
asyncio.run(_call(args, request, output_stream))
|
|
188
|
-
|
|
210
|
+
elif args.command == "list":
|
|
189
211
|
contract = default_contract(args.strategy, args.contract, input_stream)
|
|
190
212
|
route = parse_list_contract(args.strategy, contract)
|
|
191
213
|
request = cli_request(args.method, params, ReplaySafety.UNKNOWN)
|
|
192
214
|
asyncio.run(_list(request, route, output_stream, error_stream))
|
|
215
|
+
else:
|
|
216
|
+
contract = default_contract("verify-keyset", args.contract, input_stream)
|
|
217
|
+
verify_route = parse_verify_keyset_contract(contract)
|
|
218
|
+
request = cli_request(args.method, params, ReplaySafety.UNKNOWN)
|
|
219
|
+
asyncio.run(_verify_keyset(request, verify_route, output_stream))
|
|
193
220
|
except KeyboardInterrupt:
|
|
194
221
|
return _INTERRUPTED
|
|
195
222
|
except SystemExit as error:
|
|
@@ -199,6 +226,12 @@ def main( # noqa: PLR0911 - stable process-code boundary
|
|
|
199
226
|
return _USAGE
|
|
200
227
|
except BrokenPipeError:
|
|
201
228
|
return _OUTPUT_CLOSED
|
|
229
|
+
except KeysetCapabilityError as error:
|
|
230
|
+
try:
|
|
231
|
+
_write_json(output_stream, error.report.to_dict())
|
|
232
|
+
except BrokenPipeError:
|
|
233
|
+
return _OUTPUT_CLOSED
|
|
234
|
+
return _KEYSET_UNSUPPORTED if error.verdict.value == "unsupported" else _KEYSET_INCONCLUSIVE
|
|
202
235
|
except B24ApiError as error:
|
|
203
236
|
_write_json(error_stream, _safe_error(error))
|
|
204
237
|
return _CORRECTNESS
|
|
@@ -19,6 +19,7 @@ from b24api import (
|
|
|
19
19
|
RangeKeysetExecution,
|
|
20
20
|
ReplaySafety,
|
|
21
21
|
Request,
|
|
22
|
+
ResultCollectionShape,
|
|
22
23
|
ResultSelector,
|
|
23
24
|
SequentialKeysetExecution,
|
|
24
25
|
StableIntegerKeysetContract,
|
|
@@ -32,7 +33,7 @@ if TYPE_CHECKING:
|
|
|
32
33
|
|
|
33
34
|
_CONTRACT_VERSION = 1
|
|
34
35
|
_PORTAL_BATCH_CAP = 50
|
|
35
|
-
|
|
36
|
+
_DEFAULT_AUTO_KEYSET_EXECUTION = AutoKeysetExecution(StableIntegerKeysetContract())
|
|
36
37
|
|
|
37
38
|
|
|
38
39
|
class CliUsageError(ValueError):
|
|
@@ -220,10 +221,10 @@ def _keyset(raw: object) -> KeysetSpec:
|
|
|
220
221
|
def parse_keyset_execution(payload: Mapping[str, JsonValue] | None) -> KeysetExecution:
|
|
221
222
|
"""Parse the closed per-call keyset execution object."""
|
|
222
223
|
if payload is None:
|
|
223
|
-
return
|
|
224
|
+
return _DEFAULT_AUTO_KEYSET_EXECUTION
|
|
224
225
|
if not isinstance(payload, dict):
|
|
225
226
|
raise CliUsageError("execution must be an object")
|
|
226
|
-
kind = payload.get("kind", "
|
|
227
|
+
kind = payload.get("kind", "auto")
|
|
227
228
|
if not isinstance(kind, str):
|
|
228
229
|
raise CliUsageError("execution.kind must be a string")
|
|
229
230
|
if kind not in {"sequential", "range", "partitioned", "auto"}:
|
|
@@ -309,7 +310,42 @@ class ListContractRoute:
|
|
|
309
310
|
identity: IdentitySpec | None
|
|
310
311
|
page_size: int
|
|
311
312
|
mechanics: OffsetSpec | KeysetSpec | CursorSpec
|
|
312
|
-
execution: KeysetExecution =
|
|
313
|
+
execution: KeysetExecution = _DEFAULT_AUTO_KEYSET_EXECUTION
|
|
314
|
+
|
|
315
|
+
|
|
316
|
+
@dataclass(frozen=True, slots=True)
|
|
317
|
+
class VerifyKeysetContractRoute:
|
|
318
|
+
"""Fully validated explicit keyset-verifier contract."""
|
|
319
|
+
|
|
320
|
+
selector: ResultSelector
|
|
321
|
+
identity: IdentitySpec
|
|
322
|
+
keyset: KeysetSpec
|
|
323
|
+
collection_shape: ResultCollectionShape = ResultCollectionShape.SEQUENCE
|
|
324
|
+
page_size: int = _PORTAL_BATCH_CAP
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def parse_verify_keyset_contract(contract: dict[str, object]) -> VerifyKeysetContractRoute:
|
|
328
|
+
"""Validate the closed v1 verifier wire contract."""
|
|
329
|
+
_closed(
|
|
330
|
+
contract,
|
|
331
|
+
{"version", "selector", "identity", "keyset", "collection_shape", "page_size"},
|
|
332
|
+
label="verify-keyset contract",
|
|
333
|
+
)
|
|
334
|
+
selector, page_size = _common(contract, selector_required=True)
|
|
335
|
+
identity = _identity(contract.get("identity"), required=True)
|
|
336
|
+
try:
|
|
337
|
+
shape = ResultCollectionShape(
|
|
338
|
+
cast("str", contract.get("collection_shape", ResultCollectionShape.SEQUENCE.value)),
|
|
339
|
+
)
|
|
340
|
+
except (TypeError, ValueError) as error:
|
|
341
|
+
raise CliUsageError("collection_shape is invalid") from error
|
|
342
|
+
return VerifyKeysetContractRoute(
|
|
343
|
+
selector,
|
|
344
|
+
cast("IdentitySpec", identity),
|
|
345
|
+
_keyset(contract.get("keyset")),
|
|
346
|
+
shape,
|
|
347
|
+
page_size,
|
|
348
|
+
)
|
|
313
349
|
|
|
314
350
|
|
|
315
351
|
def parse_list_contract(strategy: str, contract: dict[str, object]) -> ListContractRoute:
|
|
@@ -390,11 +426,13 @@ __all__ = [
|
|
|
390
426
|
"CliUsageError",
|
|
391
427
|
"KeysetExecutionJson",
|
|
392
428
|
"ListContractRoute",
|
|
429
|
+
"VerifyKeysetContractRoute",
|
|
393
430
|
"cli_request",
|
|
394
431
|
"decode_one_object",
|
|
395
432
|
"default_contract",
|
|
396
433
|
"list_stream",
|
|
397
434
|
"parse_keyset_execution",
|
|
398
435
|
"parse_list_contract",
|
|
436
|
+
"parse_verify_keyset_contract",
|
|
399
437
|
"read_json_source",
|
|
400
438
|
]
|