b24api 2.1.0__tar.gz → 2.3.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.3.0}/PKG-INFO +122 -46
- {b24api-2.1.0 → b24api-2.3.0}/README.md +120 -44
- {b24api-2.1.0 → b24api-2.3.0}/b24api/__init__.py +114 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/_client_traversal.py +29 -4
- {b24api-2.1.0 → b24api-2.3.0}/b24api/_stream.py +28 -77
- {b24api-2.1.0 → b24api-2.3.0}/b24api/batch/engine.py +69 -12
- {b24api-2.1.0 → b24api-2.3.0}/b24api/batch/logical.py +90 -5
- {b24api-2.1.0 → b24api-2.3.0}/b24api/batch/stream.py +2 -1
- {b24api-2.1.0 → b24api-2.3.0}/b24api/cli.py +42 -5
- {b24api-2.1.0 → b24api-2.3.0}/b24api/cli_contract.py +52 -6
- {b24api-2.1.0 → b24api-2.3.0}/b24api/client.py +77 -3
- b24api-2.3.0/b24api/completion/__init__.py +5 -0
- b24api-2.3.0/b24api/completion/closure.py +31 -0
- b24api-2.3.0/b24api/completion/fast_recorder.py +195 -0
- b24api-2.3.0/b24api/completion/gate.py +479 -0
- b24api-2.3.0/b24api/completion/recorder.py +284 -0
- b24api-2.3.0/b24api/completion/reference_recorder.py +180 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/contracts/__init__.py +102 -1
- b24api-2.3.0/b24api/contracts/bounded_range.py +81 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/contracts/command.py +13 -0
- b24api-2.3.0/b24api/contracts/completion.py +158 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/contracts/dispatch.py +7 -0
- b24api-2.3.0/b24api/contracts/identity_store.py +45 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/contracts/json.py +1 -1
- b24api-2.3.0/b24api/contracts/keyset_capability.py +246 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/contracts/keyset_execution.py +1 -0
- b24api-2.3.0/b24api/contracts/page.py +70 -0
- b24api-2.3.0/b24api/contracts/page_stop.py +68 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/contracts/policy.py +2 -0
- b24api-2.3.0/b24api/contracts/positional.py +236 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/contracts/reference.py +19 -1
- {b24api-2.1.0 → b24api-2.3.0}/b24api/contracts/report.py +24 -41
- {b24api-2.1.0 → b24api-2.3.0}/b24api/contracts/request.py +62 -10
- {b24api-2.1.0 → b24api-2.3.0}/b24api/contracts/response.py +4 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/contracts/traversal.py +148 -1
- b24api-2.3.0/b24api/contracts/violation.py +102 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/errors.py +152 -4
- {b24api-2.1.0 → b24api-2.3.0}/b24api/execution/__init__.py +14 -1
- {b24api-2.1.0 → b24api-2.3.0}/b24api/execution/context.py +32 -8
- {b24api-2.1.0 → b24api-2.3.0}/b24api/execution/executor.py +57 -69
- {b24api-2.1.0 → b24api-2.3.0}/b24api/execution/failure.py +26 -9
- {b24api-2.1.0 → b24api-2.3.0}/b24api/execution/rate.py +134 -18
- {b24api-2.1.0 → b24api-2.3.0}/b24api/execution/snapshot.py +5 -0
- b24api-2.3.0/b24api/execution/throttle.py +53 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/redaction.py +1 -1
- {b24api-2.1.0 → b24api-2.3.0}/b24api/references/binding.py +25 -5
- b24api-2.3.0/b24api/references/dispatch.py +695 -0
- b24api-2.3.0/b24api/references/dispatch_plan.py +27 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/references/facade.py +59 -68
- {b24api-2.1.0 → b24api-2.3.0}/b24api/references/fanout.py +4 -4
- {b24api-2.1.0 → b24api-2.3.0}/b24api/references/outcome.py +27 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/references/scheduler.py +184 -44
- {b24api-2.1.0 → b24api-2.3.0}/b24api/references/stream.py +49 -26
- {b24api-2.1.0 → b24api-2.3.0}/b24api/references/support.py +67 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/testing/__init__.py +3 -0
- b24api-2.3.0/b24api/testing/scripted.py +147 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/testing/transport.py +39 -8
- {b24api-2.1.0 → b24api-2.3.0}/b24api/transport/base.py +58 -4
- {b24api-2.1.0 → b24api-2.3.0}/b24api/transport/httpx.py +102 -13
- b24api-2.3.0/b24api/transport/logging_shield.py +244 -0
- b24api-2.3.0/b24api/transport/protocol.py +279 -0
- b24api-2.3.0/b24api/traversal/control_preflight.py +81 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/counted.py +65 -5
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/counted_batch.py +51 -23
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/cursor.py +26 -7
- b24api-2.3.0/b24api/traversal/cursor_domain.py +61 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/driver.py +124 -98
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/facade.py +63 -44
- b24api-2.3.0/b24api/traversal/facade_support.py +36 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/identity.py +74 -28
- b24api-2.3.0/b24api/traversal/identity_ledger.py +59 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/keyset.py +11 -6
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/keyset_auto.py +1 -6
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/keyset_capability.py +3 -3
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/keyset_eligibility.py +50 -13
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/keyset_fast_plan.py +4 -4
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/keyset_fast_stream.py +46 -9
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/keyset_observation.py +5 -23
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/keyset_reporting.py +2 -3
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/keyset_scheduler.py +52 -55
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/keyset_step.py +46 -3
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/keyset_transaction_contract.py +4 -4
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/keyset_transactions.py +56 -31
- b24api-2.3.0/b24api/traversal/keyset_verifier.py +528 -0
- b24api-2.3.0/b24api/traversal/offset_rules.py +76 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/ordered_admission.py +5 -6
- b24api-2.3.0/b24api/traversal/page_adaptation.py +90 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/page_validation.py +46 -9
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/plans.py +43 -3
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/sequential.py +76 -22
- b24api-2.3.0/b24api/traversal/sparse.py +55 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/stream.py +99 -5
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/values.py +59 -36
- {b24api-2.1.0 → b24api-2.3.0/b24api.egg-info}/PKG-INFO +122 -46
- {b24api-2.1.0 → b24api-2.3.0}/b24api.egg-info/SOURCES.txt +26 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api.egg-info/requires.txt +1 -1
- {b24api-2.1.0 → b24api-2.3.0}/pyproject.toml +3 -2
- b24api-2.1.0/b24api/references/dispatch.py +0 -435
- b24api-2.1.0/b24api/transport/protocol.py +0 -157
- {b24api-2.1.0 → b24api-2.3.0}/LICENSE +0 -0
- {b24api-2.1.0 → b24api-2.3.0}/MANIFEST.in +0 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/_audit.py +0 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/_error_types.py +0 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/batch/__init__.py +0 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/batch/facade.py +0 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/batch/outcome.py +0 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/contracts/stream.py +0 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/contracts/wire.py +0 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/encoding.py +0 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/execution/cleanup.py +0 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/references/__init__.py +0 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/settings.py +0 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/testing/_isolation.py +0 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/transport/__init__.py +0 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/__init__.py +0 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/keyset_costs.py +0 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/keyset_partition.py +0 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api/traversal/keyset_range.py +0 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api.egg-info/dependency_links.txt +0 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api.egg-info/entry_points.txt +0 -0
- {b24api-2.1.0 → b24api-2.3.0}/b24api.egg-info/top_level.txt +0 -0
- {b24api-2.1.0 → b24api-2.3.0}/setup.cfg +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: b24api
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: Bitrix24 API
|
|
5
5
|
Requires-Python: >=3.12
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
7
7
|
License-File: LICENSE
|
|
8
|
-
Requires-Dist: httpx[http2]
|
|
8
|
+
Requires-Dist: httpx[http2]<0.29,>=0.28.1
|
|
9
9
|
Requires-Dist: pydantic>=2.11.7
|
|
10
10
|
Requires-Dist: pydantic-settings>=2.10.1
|
|
11
11
|
Dynamic: license-file
|
|
@@ -29,10 +29,10 @@ work so its HTTP/2 connection pool and rate state are reused.
|
|
|
29
29
|
|
|
30
30
|
<!-- tested: tests/client_v2_test.py::test_call_and_call_response_have_stable_detached_types -->
|
|
31
31
|
```python
|
|
32
|
-
from b24api import Bitrix24, Request
|
|
32
|
+
from b24api import Bitrix24, Request, RouteKind
|
|
33
33
|
|
|
34
34
|
async with Bitrix24() as client:
|
|
35
|
-
profile = await client.call(Request("profile"))
|
|
35
|
+
profile = await client.call(Request("profile", route=RouteKind.BARE))
|
|
36
36
|
```
|
|
37
37
|
|
|
38
38
|
The client owns its default transport. An injected transport remains caller-owned. `aclose()` is
|
|
@@ -48,15 +48,17 @@ The operation is explicit and never hides malformed JSON by falling back to byte
|
|
|
48
48
|
|
|
49
49
|
<!-- tested: tests/client_findings_3_test.py::test_binary_call_returns_every_success_byte_without_json_sniffing -->
|
|
50
50
|
```python
|
|
51
|
-
|
|
51
|
+
from b24api import RouteKind
|
|
52
|
+
archive = await client.call_bytes(Request("example.export.download", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE))
|
|
52
53
|
payload = archive.body
|
|
53
54
|
```
|
|
54
55
|
|
|
55
56
|
<!-- tested: tests/client_v2_test.py::test_call_and_call_response_have_stable_detached_types -->
|
|
56
57
|
```python
|
|
58
|
+
from b24api import RouteKind
|
|
57
59
|
from b24api import ReplaySafety
|
|
58
60
|
|
|
59
|
-
request = Request("example.item.get", {"id": 7}, ReplaySafety.SAFE)
|
|
61
|
+
request = Request("example.item.get", {"id": 7}, ReplaySafety.SAFE, route=RouteKind.BARE)
|
|
60
62
|
decoded = await client.call(request)
|
|
61
63
|
response = await client.call_response(request)
|
|
62
64
|
```
|
|
@@ -97,11 +99,12 @@ matching a result to the object, file, chat or database row that produced its re
|
|
|
97
99
|
|
|
98
100
|
<!-- tested: tests/client_v2_test.py::test_logical_batch_is_unbounded_ordered_and_correlation_is_strictly_off_wire -->
|
|
99
101
|
```python
|
|
102
|
+
from b24api import RouteKind
|
|
100
103
|
from b24api import Command, CommandSuccess
|
|
101
104
|
|
|
102
105
|
commands = (
|
|
103
106
|
Command(
|
|
104
|
-
Request("example.item.get", {"id": item_id}, ReplaySafety.SAFE),
|
|
107
|
+
Request("example.item.get", {"id": item_id}, ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
105
108
|
correlation=item_id,
|
|
106
109
|
)
|
|
107
110
|
for item_id in source_ids
|
|
@@ -139,23 +142,34 @@ more specialized mechanics have explicit names and explicit preconditions.
|
|
|
139
142
|
|
|
140
143
|
| Operation | Use it when | Network mechanics | Completion proof |
|
|
141
144
|
|---|---|---|---|
|
|
142
|
-
| `iter_list` | The method supports ordinary offset pagination. |
|
|
145
|
+
| `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
146
|
| `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. |
|
|
147
|
+
| `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
148
|
| `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. |
|
|
149
|
+
| `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
150
|
| `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
151
|
|
|
148
152
|
`page_size` is a local decoded-page cap. It is sent to Bitrix only when you provide the endpoint's
|
|
149
153
|
exact `limit_path`; the client never guesses method-specific parameter names.
|
|
150
154
|
|
|
155
|
+
### List traversal comparison
|
|
156
|
+
|
|
157
|
+

|
|
158
|
+
|
|
151
159
|
### Sequential offset
|
|
152
160
|
|
|
153
161
|
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
|
|
162
|
+
with an empty page. For ordinary counted Bitrix list endpoints, `next` is an offset for the next
|
|
163
|
+
LIMIT/OFFSET page, not a keyset cursor. Producing `total` involves a separate server-side count
|
|
164
|
+
query in addition to retrieving the page. These database operations are performed inside the same
|
|
165
|
+
REST request: the client does not issue an additional HTTP call just for the count. This distinction
|
|
166
|
+
matters for performance: not making a separate HTTP count call does **not** mean avoiding server-side
|
|
167
|
+
COUNT work. `iter_list` does not suppress that work; a returned `total` is observational and does not
|
|
168
|
+
control this strategy's completion. Exact database implementation is endpoint-specific.
|
|
156
169
|
|
|
157
170
|
<!-- tested: tests/client_v2_test.py::test_iter_list_is_sequential_mechanics_only_and_report_is_post_cleanup -->
|
|
158
171
|
```python
|
|
172
|
+
from b24api import RouteKind
|
|
159
173
|
from b24api import IdentityCoercion, IdentitySpec, ResultSelector
|
|
160
174
|
|
|
161
175
|
identity = IdentitySpec(
|
|
@@ -166,7 +180,7 @@ identity = IdentitySpec(
|
|
|
166
180
|
)
|
|
167
181
|
|
|
168
182
|
stream = client.iter_list(
|
|
169
|
-
Request("example.item.list", replay_safety=ReplaySafety.SAFE),
|
|
183
|
+
Request("example.item.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
170
184
|
selector=ResultSelector(("items",)),
|
|
171
185
|
identity=identity,
|
|
172
186
|
)
|
|
@@ -184,10 +198,11 @@ sequence and records that degradation in the operation report.
|
|
|
184
198
|
|
|
185
199
|
<!-- tested: tests/client_findings_3_test.py::test_shape_rejection_is_retained_as_zero_admission_page_evidence -->
|
|
186
200
|
```python
|
|
201
|
+
from b24api import RouteKind
|
|
187
202
|
from b24api import ResultCollectionShape
|
|
188
203
|
|
|
189
204
|
stream = client.iter_list(
|
|
190
|
-
Request("example.dictionary.list", replay_safety=ReplaySafety.SAFE),
|
|
205
|
+
Request("example.dictionary.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
191
206
|
selector=ResultSelector(("items",)),
|
|
192
207
|
collection_shape=ResultCollectionShape.MAPPING_VALUES,
|
|
193
208
|
)
|
|
@@ -204,8 +219,9 @@ bounded physical batches.
|
|
|
204
219
|
|
|
205
220
|
<!-- tested: tests/client_v2_test.py::test_counted_traversal_preserves_frozen_request_shape_and_exact_identity -->
|
|
206
221
|
```python
|
|
222
|
+
from b24api import RouteKind
|
|
207
223
|
stream = client.iter_list_counted(
|
|
208
|
-
Request("example.item.list", replay_safety=ReplaySafety.SAFE),
|
|
224
|
+
Request("example.item.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
209
225
|
selector=ResultSelector(("items",)),
|
|
210
226
|
identity=identity,
|
|
211
227
|
page_size=50,
|
|
@@ -216,35 +232,79 @@ stream = client.iter_list_counted(
|
|
|
216
232
|
Use it only when `total` is exact for the supplied filter and offset pages are stable. Any missing
|
|
217
233
|
range, overlap, duplicate identity or total contradiction raises `IncompleteTraversalError`.
|
|
218
234
|
|
|
235
|
+
Physical batching reduces HTTP exchanges, but does not suppress server-side COUNT in ordinary
|
|
236
|
+
counted list subrequests. Each command still performs its own offset page retrieval and associated
|
|
237
|
+
total calculation on the server. Do not confuse batching these commands with a no-count traversal.
|
|
238
|
+
|
|
219
239
|
### No-count keyset
|
|
220
240
|
|
|
221
|
-
Keyset traversal
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
before any row is emitted, so partial consumption
|
|
225
|
-
|
|
241
|
+
Keyset traversal uses automatic execution by default. Omitting `execution` is equivalent to
|
|
242
|
+
`AutoKeysetExecution(StableIntegerKeysetContract())`: the client captures both ordered boundaries,
|
|
243
|
+
then selects boundary-only, sequential, range, or partitioned execution from the observed geometry
|
|
244
|
+
and available policy capacity. Planning completes before any row is emitted, so partial consumption
|
|
245
|
+
still pays that barrier cost. A sequential selection made by auto is a cost decision; a failed or
|
|
246
|
+
contradictory fast plan is never silently restarted as sequential.
|
|
247
|
+
|
|
248
|
+
By using the default, the caller asserts that the endpoint has a stable, unique integer key, honors
|
|
249
|
+
strict numeric bounds and ordering, and satisfies empty-confirmation completion. Concurrent mutation
|
|
250
|
+
outside the captured middle is handled by the finishing sweep; mutation inside it is outside this
|
|
251
|
+
assertion. Pass `SequentialKeysetExecution()` explicitly when an endpoint cannot satisfy the fast
|
|
252
|
+
contract or when the previous request-by-request behavior is required.
|
|
253
|
+
|
|
254
|
+
Static incompatibility with the auto contract raises `CapabilityError` from the
|
|
255
|
+
`iter_list_keyset(...)` call before iteration begins. A portal that accepts but contradicts the
|
|
256
|
+
declared controls fails before emission with `IncompleteTraversalError`; auto never restarts that
|
|
257
|
+
operation silently. `KeysetTraversal` inside reference traversal remains sequential-only. The
|
|
258
|
+
terminal report now includes `keyset_execution` for omitted-execution keyset calls so consumers can
|
|
259
|
+
see the requested and selected plan.
|
|
260
|
+
|
|
261
|
+
Use `await client.verify_keyset_capability(...)` as a development/CI/staging guard on a stable
|
|
262
|
+
representative fixture. It performs five strict-bound checks and returns only a `VERIFIED` report;
|
|
263
|
+
unsupported and inconclusive verdicts raise `KeysetCapabilityError`. The ordinary
|
|
264
|
+
`iter_list_keyset()` remains a separate caller-asserted operation with zero verifier canaries and
|
|
265
|
+
may emit a partial prefix before a late endpoint contradiction is detected.
|
|
266
|
+
Keep the guard beside the traversal, for example under
|
|
267
|
+
`if os.environ.get("ENV") != "PROD":`; set `ENV=PROD` only after qualifying the exact portal,
|
|
268
|
+
credentials, method, request/filter, identity, ordering representation, and page cap.
|
|
269
|
+
|
|
270
|
+
Every list operation also accepts an immutable `PageAdapter` strategy. The adapter synchronously
|
|
271
|
+
maps selected frozen items using sibling result metadata while preserving cardinality, order and
|
|
272
|
+
configured identities. The identity adapter is the default and preserves existing JSON output.
|
|
226
273
|
|
|
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
274
|
An advisory `total` may only raise an automatic cost estimate and never proves completion. Reports
|
|
231
275
|
record the selected strategy and reason: unbounded auto continuation has the same
|
|
232
276
|
`ordered_prefix_only` assurance as sequential traversal, while bounded plans additionally report
|
|
233
|
-
`
|
|
277
|
+
`caller_asserted_bounds`. Runtime traversal does not perform verifier canaries.
|
|
234
278
|
|
|
235
|
-
<!-- tested: tests/
|
|
279
|
+
<!-- tested: tests/keyset_fast_test.py::test_omitted_execution_defaults_to_auto -->
|
|
236
280
|
```python
|
|
237
|
-
|
|
281
|
+
import os
|
|
282
|
+
|
|
283
|
+
from b24api import KeysetSpec, ParameterPath, ReplaySafety, Request, ResultSelector, RouteKind
|
|
284
|
+
|
|
285
|
+
request = Request("example.item.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE)
|
|
286
|
+
selector = ResultSelector(("items",))
|
|
287
|
+
keyset = KeysetSpec(
|
|
288
|
+
filter_path=ParameterPath(("filter",)),
|
|
289
|
+
order_path=ParameterPath(("order",)),
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
if os.environ.get("ENV") != "PROD":
|
|
293
|
+
# Accepting an ID filter does not prove strict bounds or ordering.
|
|
294
|
+
await client.verify_keyset_capability(
|
|
295
|
+
request,
|
|
296
|
+
selector=selector,
|
|
297
|
+
identity=identity,
|
|
298
|
+
page_size=50,
|
|
299
|
+
keyset=keyset,
|
|
300
|
+
)
|
|
238
301
|
|
|
239
302
|
stream = client.iter_list_keyset(
|
|
240
|
-
|
|
241
|
-
selector=
|
|
303
|
+
request,
|
|
304
|
+
selector=selector,
|
|
242
305
|
identity=identity,
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
order_path=ParameterPath(("order",)),
|
|
246
|
-
),
|
|
247
|
-
execution=AutoKeysetExecution(contract=StableIntegerKeysetContract()),
|
|
306
|
+
page_size=50,
|
|
307
|
+
keyset=keyset,
|
|
248
308
|
)
|
|
249
309
|
```
|
|
250
310
|
|
|
@@ -255,10 +315,11 @@ message-list methods.
|
|
|
255
315
|
|
|
256
316
|
<!-- tested: tests/client_v2_test.py::test_keyset_and_cursor_are_explicit_strict_alternatives -->
|
|
257
317
|
```python
|
|
318
|
+
from b24api import RouteKind
|
|
258
319
|
from b24api import CursorSpec, ParameterPath
|
|
259
320
|
|
|
260
321
|
stream = client.iter_list_cursor(
|
|
261
|
-
Request("example.message.list", replay_safety=ReplaySafety.SAFE),
|
|
322
|
+
Request("example.message.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
262
323
|
selector=ResultSelector(("items",)),
|
|
263
324
|
cursor=CursorSpec(
|
|
264
325
|
parameter_path=ParameterPath(("LAST_ID",)),
|
|
@@ -273,6 +334,11 @@ stream = client.iter_list_cursor(
|
|
|
273
334
|
Cursor values must be unique and strictly monotonic. If an endpoint exposes only a non-unique
|
|
274
335
|
boundary, use an application-owned direct-call workflow or supply a unique tie-breaker.
|
|
275
336
|
|
|
337
|
+
For multiple parent-bound cursor chains, `iter_cursors()` keeps cursor progress and correlation
|
|
338
|
+
isolated per binding while ready pages share the physical batch queue.
|
|
339
|
+
|
|
340
|
+

|
|
341
|
+
|
|
276
342
|
See [architecture](docs/architecture.md), [migration](docs/migration.md),
|
|
277
343
|
[performance](docs/performance.md), and [endpoint recipes](docs/recipes.md) for the complete
|
|
278
344
|
contracts and selection guidance.
|
|
@@ -283,8 +349,11 @@ contracts and selection guidance.
|
|
|
283
349
|
client remains unaware of entity types: a binding can represent a deal, lead, chat or any other
|
|
284
350
|
caller-defined parent.
|
|
285
351
|
|
|
352
|
+

|
|
353
|
+
|
|
286
354
|
<!-- tested: tests/client_v2_test.py::test_bound_references_apply_nested_updates_off_wire_and_emit_exact_completion -->
|
|
287
355
|
```python
|
|
356
|
+
from b24api import RouteKind
|
|
288
357
|
from b24api import (
|
|
289
358
|
BatchDispatch,
|
|
290
359
|
Binding,
|
|
@@ -305,7 +374,7 @@ bindings = (
|
|
|
305
374
|
)
|
|
306
375
|
|
|
307
376
|
stream = client.iter_references(
|
|
308
|
-
Request("example.comment.list", replay_safety=ReplaySafety.SAFE),
|
|
377
|
+
Request("example.comment.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
309
378
|
bindings,
|
|
310
379
|
traversal=SequentialTraversal(selector=ResultSelector(("items",)), identity=identity),
|
|
311
380
|
dispatch=BatchDispatch(batch_size=25, concurrency=2),
|
|
@@ -325,6 +394,7 @@ under different parents are not conflated.
|
|
|
325
394
|
|
|
326
395
|
<!-- tested: tests/client_v2_test.py::test_bound_references_apply_nested_updates_off_wire_and_emit_exact_completion -->
|
|
327
396
|
```python
|
|
397
|
+
from b24api import RouteKind
|
|
328
398
|
from b24api import (
|
|
329
399
|
Binding,
|
|
330
400
|
CursorSpec,
|
|
@@ -346,7 +416,7 @@ chat_bindings = (
|
|
|
346
416
|
)
|
|
347
417
|
|
|
348
418
|
messages = client.iter_references(
|
|
349
|
-
Request("example.message.list", replay_safety=ReplaySafety.SAFE),
|
|
419
|
+
Request("example.message.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
350
420
|
chat_bindings,
|
|
351
421
|
traversal=CursorTraversal(
|
|
352
422
|
selector=ResultSelector(("items",)),
|
|
@@ -389,13 +459,19 @@ and publish the same final report where the Python exception type permits it.
|
|
|
389
459
|
## Resource boundaries
|
|
390
460
|
|
|
391
461
|
`ExecutionPolicy` bounds requests, pages, elapsed time, attempts, decompressed response bytes,
|
|
392
|
-
buffered commands and rows, direct concurrency and active
|
|
393
|
-
is 16 MiB and is enforced while streaming, before JSON
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
462
|
+
buffered commands and rows, retained unordered identity keys, direct concurrency and active
|
|
463
|
+
references. The default response ceiling is 16 MiB and is enforced while streaming, before JSON
|
|
464
|
+
decoding.
|
|
465
|
+
|
|
466
|
+
Sequential, counted, and multi-reference exact traversal retain at most `max_identity_keys`
|
|
467
|
+
observed identities per operation in memory (100,000 by default). All active reference bindings
|
|
468
|
+
share that ceiling. A page that would exceed it is rejected atomically with typed budget evidence.
|
|
469
|
+
Set a larger finite ceiling when the expected aggregate cardinality is known, or pass
|
|
470
|
+
`identity_store=` to `iter_list`/`iter_list_counted` so a caller-owned `IdentityStore` (for example a
|
|
471
|
+
SQLite table keyed by `identity_store_key(...)`) proves uniqueness while in-process identity memory
|
|
472
|
+
stays bounded by one page; the client never closes that store. Repeated-page detection still keeps
|
|
473
|
+
one short fingerprint per page, so raise `max_pages` deliberately for very long traversals.
|
|
474
|
+
Strict keyset and cursor traversal retain only monotonic progression state when sufficient.
|
|
399
475
|
|
|
400
476
|
## CLI
|
|
401
477
|
|
|
@@ -404,10 +480,10 @@ errors go to stderr. Credentials come only from `Settings` and cannot be passed
|
|
|
404
480
|
|
|
405
481
|
<!-- tested-console: tests/cli_test.py::test_call_routes_replay_safety_and_keeps_success_data_on_stdout -->
|
|
406
482
|
```console
|
|
407
|
-
b24api call profile
|
|
408
|
-
b24api call example.item.get --params '{"id":7}' --raw --replay-safety safe
|
|
409
|
-
b24api list example.item.list --params @params.json
|
|
410
|
-
b24api list example.item.list --strategy counted --contract @counted-contract.json
|
|
483
|
+
b24api call profile --route bare
|
|
484
|
+
b24api call example.item.get --route bare --params '{"id":7}' --raw --replay-safety safe
|
|
485
|
+
b24api list example.item.list --route bare --params @params.json
|
|
486
|
+
b24api list example.item.list --route bare --strategy counted --contract @counted-contract.json
|
|
411
487
|
```
|
|
412
488
|
|
|
413
489
|
The `--raw` CLI option selects the response envelope; it does not alter the Python API. Advanced
|
|
@@ -17,10 +17,10 @@ work so its HTTP/2 connection pool and rate state are reused.
|
|
|
17
17
|
|
|
18
18
|
<!-- tested: tests/client_v2_test.py::test_call_and_call_response_have_stable_detached_types -->
|
|
19
19
|
```python
|
|
20
|
-
from b24api import Bitrix24, Request
|
|
20
|
+
from b24api import Bitrix24, Request, RouteKind
|
|
21
21
|
|
|
22
22
|
async with Bitrix24() as client:
|
|
23
|
-
profile = await client.call(Request("profile"))
|
|
23
|
+
profile = await client.call(Request("profile", route=RouteKind.BARE))
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
The client owns its default transport. An injected transport remains caller-owned. `aclose()` is
|
|
@@ -36,15 +36,17 @@ The operation is explicit and never hides malformed JSON by falling back to byte
|
|
|
36
36
|
|
|
37
37
|
<!-- tested: tests/client_findings_3_test.py::test_binary_call_returns_every_success_byte_without_json_sniffing -->
|
|
38
38
|
```python
|
|
39
|
-
|
|
39
|
+
from b24api import RouteKind
|
|
40
|
+
archive = await client.call_bytes(Request("example.export.download", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE))
|
|
40
41
|
payload = archive.body
|
|
41
42
|
```
|
|
42
43
|
|
|
43
44
|
<!-- tested: tests/client_v2_test.py::test_call_and_call_response_have_stable_detached_types -->
|
|
44
45
|
```python
|
|
46
|
+
from b24api import RouteKind
|
|
45
47
|
from b24api import ReplaySafety
|
|
46
48
|
|
|
47
|
-
request = Request("example.item.get", {"id": 7}, ReplaySafety.SAFE)
|
|
49
|
+
request = Request("example.item.get", {"id": 7}, ReplaySafety.SAFE, route=RouteKind.BARE)
|
|
48
50
|
decoded = await client.call(request)
|
|
49
51
|
response = await client.call_response(request)
|
|
50
52
|
```
|
|
@@ -85,11 +87,12 @@ matching a result to the object, file, chat or database row that produced its re
|
|
|
85
87
|
|
|
86
88
|
<!-- tested: tests/client_v2_test.py::test_logical_batch_is_unbounded_ordered_and_correlation_is_strictly_off_wire -->
|
|
87
89
|
```python
|
|
90
|
+
from b24api import RouteKind
|
|
88
91
|
from b24api import Command, CommandSuccess
|
|
89
92
|
|
|
90
93
|
commands = (
|
|
91
94
|
Command(
|
|
92
|
-
Request("example.item.get", {"id": item_id}, ReplaySafety.SAFE),
|
|
95
|
+
Request("example.item.get", {"id": item_id}, ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
93
96
|
correlation=item_id,
|
|
94
97
|
)
|
|
95
98
|
for item_id in source_ids
|
|
@@ -127,23 +130,34 @@ more specialized mechanics have explicit names and explicit preconditions.
|
|
|
127
130
|
|
|
128
131
|
| Operation | Use it when | Network mechanics | Completion proof |
|
|
129
132
|
|---|---|---|---|
|
|
130
|
-
| `iter_list` | The method supports ordinary offset pagination. |
|
|
133
|
+
| `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
134
|
| `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. |
|
|
135
|
+
| `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
136
|
| `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. |
|
|
137
|
+
| `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
138
|
| `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
139
|
|
|
136
140
|
`page_size` is a local decoded-page cap. It is sent to Bitrix only when you provide the endpoint's
|
|
137
141
|
exact `limit_path`; the client never guesses method-specific parameter names.
|
|
138
142
|
|
|
143
|
+
### List traversal comparison
|
|
144
|
+
|
|
145
|
+

|
|
146
|
+
|
|
139
147
|
### Sequential offset
|
|
140
148
|
|
|
141
149
|
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
|
|
150
|
+
with an empty page. For ordinary counted Bitrix list endpoints, `next` is an offset for the next
|
|
151
|
+
LIMIT/OFFSET page, not a keyset cursor. Producing `total` involves a separate server-side count
|
|
152
|
+
query in addition to retrieving the page. These database operations are performed inside the same
|
|
153
|
+
REST request: the client does not issue an additional HTTP call just for the count. This distinction
|
|
154
|
+
matters for performance: not making a separate HTTP count call does **not** mean avoiding server-side
|
|
155
|
+
COUNT work. `iter_list` does not suppress that work; a returned `total` is observational and does not
|
|
156
|
+
control this strategy's completion. Exact database implementation is endpoint-specific.
|
|
144
157
|
|
|
145
158
|
<!-- tested: tests/client_v2_test.py::test_iter_list_is_sequential_mechanics_only_and_report_is_post_cleanup -->
|
|
146
159
|
```python
|
|
160
|
+
from b24api import RouteKind
|
|
147
161
|
from b24api import IdentityCoercion, IdentitySpec, ResultSelector
|
|
148
162
|
|
|
149
163
|
identity = IdentitySpec(
|
|
@@ -154,7 +168,7 @@ identity = IdentitySpec(
|
|
|
154
168
|
)
|
|
155
169
|
|
|
156
170
|
stream = client.iter_list(
|
|
157
|
-
Request("example.item.list", replay_safety=ReplaySafety.SAFE),
|
|
171
|
+
Request("example.item.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
158
172
|
selector=ResultSelector(("items",)),
|
|
159
173
|
identity=identity,
|
|
160
174
|
)
|
|
@@ -172,10 +186,11 @@ sequence and records that degradation in the operation report.
|
|
|
172
186
|
|
|
173
187
|
<!-- tested: tests/client_findings_3_test.py::test_shape_rejection_is_retained_as_zero_admission_page_evidence -->
|
|
174
188
|
```python
|
|
189
|
+
from b24api import RouteKind
|
|
175
190
|
from b24api import ResultCollectionShape
|
|
176
191
|
|
|
177
192
|
stream = client.iter_list(
|
|
178
|
-
Request("example.dictionary.list", replay_safety=ReplaySafety.SAFE),
|
|
193
|
+
Request("example.dictionary.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
179
194
|
selector=ResultSelector(("items",)),
|
|
180
195
|
collection_shape=ResultCollectionShape.MAPPING_VALUES,
|
|
181
196
|
)
|
|
@@ -192,8 +207,9 @@ bounded physical batches.
|
|
|
192
207
|
|
|
193
208
|
<!-- tested: tests/client_v2_test.py::test_counted_traversal_preserves_frozen_request_shape_and_exact_identity -->
|
|
194
209
|
```python
|
|
210
|
+
from b24api import RouteKind
|
|
195
211
|
stream = client.iter_list_counted(
|
|
196
|
-
Request("example.item.list", replay_safety=ReplaySafety.SAFE),
|
|
212
|
+
Request("example.item.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
197
213
|
selector=ResultSelector(("items",)),
|
|
198
214
|
identity=identity,
|
|
199
215
|
page_size=50,
|
|
@@ -204,35 +220,79 @@ stream = client.iter_list_counted(
|
|
|
204
220
|
Use it only when `total` is exact for the supplied filter and offset pages are stable. Any missing
|
|
205
221
|
range, overlap, duplicate identity or total contradiction raises `IncompleteTraversalError`.
|
|
206
222
|
|
|
223
|
+
Physical batching reduces HTTP exchanges, but does not suppress server-side COUNT in ordinary
|
|
224
|
+
counted list subrequests. Each command still performs its own offset page retrieval and associated
|
|
225
|
+
total calculation on the server. Do not confuse batching these commands with a no-count traversal.
|
|
226
|
+
|
|
207
227
|
### No-count keyset
|
|
208
228
|
|
|
209
|
-
Keyset traversal
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
before any row is emitted, so partial consumption
|
|
213
|
-
|
|
229
|
+
Keyset traversal uses automatic execution by default. Omitting `execution` is equivalent to
|
|
230
|
+
`AutoKeysetExecution(StableIntegerKeysetContract())`: the client captures both ordered boundaries,
|
|
231
|
+
then selects boundary-only, sequential, range, or partitioned execution from the observed geometry
|
|
232
|
+
and available policy capacity. Planning completes before any row is emitted, so partial consumption
|
|
233
|
+
still pays that barrier cost. A sequential selection made by auto is a cost decision; a failed or
|
|
234
|
+
contradictory fast plan is never silently restarted as sequential.
|
|
235
|
+
|
|
236
|
+
By using the default, the caller asserts that the endpoint has a stable, unique integer key, honors
|
|
237
|
+
strict numeric bounds and ordering, and satisfies empty-confirmation completion. Concurrent mutation
|
|
238
|
+
outside the captured middle is handled by the finishing sweep; mutation inside it is outside this
|
|
239
|
+
assertion. Pass `SequentialKeysetExecution()` explicitly when an endpoint cannot satisfy the fast
|
|
240
|
+
contract or when the previous request-by-request behavior is required.
|
|
241
|
+
|
|
242
|
+
Static incompatibility with the auto contract raises `CapabilityError` from the
|
|
243
|
+
`iter_list_keyset(...)` call before iteration begins. A portal that accepts but contradicts the
|
|
244
|
+
declared controls fails before emission with `IncompleteTraversalError`; auto never restarts that
|
|
245
|
+
operation silently. `KeysetTraversal` inside reference traversal remains sequential-only. The
|
|
246
|
+
terminal report now includes `keyset_execution` for omitted-execution keyset calls so consumers can
|
|
247
|
+
see the requested and selected plan.
|
|
248
|
+
|
|
249
|
+
Use `await client.verify_keyset_capability(...)` as a development/CI/staging guard on a stable
|
|
250
|
+
representative fixture. It performs five strict-bound checks and returns only a `VERIFIED` report;
|
|
251
|
+
unsupported and inconclusive verdicts raise `KeysetCapabilityError`. The ordinary
|
|
252
|
+
`iter_list_keyset()` remains a separate caller-asserted operation with zero verifier canaries and
|
|
253
|
+
may emit a partial prefix before a late endpoint contradiction is detected.
|
|
254
|
+
Keep the guard beside the traversal, for example under
|
|
255
|
+
`if os.environ.get("ENV") != "PROD":`; set `ENV=PROD` only after qualifying the exact portal,
|
|
256
|
+
credentials, method, request/filter, identity, ordering representation, and page cap.
|
|
257
|
+
|
|
258
|
+
Every list operation also accepts an immutable `PageAdapter` strategy. The adapter synchronously
|
|
259
|
+
maps selected frozen items using sibling result metadata while preserving cardinality, order and
|
|
260
|
+
configured identities. The identity adapter is the default and preserves existing JSON output.
|
|
214
261
|
|
|
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
262
|
An advisory `total` may only raise an automatic cost estimate and never proves completion. Reports
|
|
219
263
|
record the selected strategy and reason: unbounded auto continuation has the same
|
|
220
264
|
`ordered_prefix_only` assurance as sequential traversal, while bounded plans additionally report
|
|
221
|
-
`
|
|
265
|
+
`caller_asserted_bounds`. Runtime traversal does not perform verifier canaries.
|
|
222
266
|
|
|
223
|
-
<!-- tested: tests/
|
|
267
|
+
<!-- tested: tests/keyset_fast_test.py::test_omitted_execution_defaults_to_auto -->
|
|
224
268
|
```python
|
|
225
|
-
|
|
269
|
+
import os
|
|
270
|
+
|
|
271
|
+
from b24api import KeysetSpec, ParameterPath, ReplaySafety, Request, ResultSelector, RouteKind
|
|
272
|
+
|
|
273
|
+
request = Request("example.item.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE)
|
|
274
|
+
selector = ResultSelector(("items",))
|
|
275
|
+
keyset = KeysetSpec(
|
|
276
|
+
filter_path=ParameterPath(("filter",)),
|
|
277
|
+
order_path=ParameterPath(("order",)),
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
if os.environ.get("ENV") != "PROD":
|
|
281
|
+
# Accepting an ID filter does not prove strict bounds or ordering.
|
|
282
|
+
await client.verify_keyset_capability(
|
|
283
|
+
request,
|
|
284
|
+
selector=selector,
|
|
285
|
+
identity=identity,
|
|
286
|
+
page_size=50,
|
|
287
|
+
keyset=keyset,
|
|
288
|
+
)
|
|
226
289
|
|
|
227
290
|
stream = client.iter_list_keyset(
|
|
228
|
-
|
|
229
|
-
selector=
|
|
291
|
+
request,
|
|
292
|
+
selector=selector,
|
|
230
293
|
identity=identity,
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
order_path=ParameterPath(("order",)),
|
|
234
|
-
),
|
|
235
|
-
execution=AutoKeysetExecution(contract=StableIntegerKeysetContract()),
|
|
294
|
+
page_size=50,
|
|
295
|
+
keyset=keyset,
|
|
236
296
|
)
|
|
237
297
|
```
|
|
238
298
|
|
|
@@ -243,10 +303,11 @@ message-list methods.
|
|
|
243
303
|
|
|
244
304
|
<!-- tested: tests/client_v2_test.py::test_keyset_and_cursor_are_explicit_strict_alternatives -->
|
|
245
305
|
```python
|
|
306
|
+
from b24api import RouteKind
|
|
246
307
|
from b24api import CursorSpec, ParameterPath
|
|
247
308
|
|
|
248
309
|
stream = client.iter_list_cursor(
|
|
249
|
-
Request("example.message.list", replay_safety=ReplaySafety.SAFE),
|
|
310
|
+
Request("example.message.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
250
311
|
selector=ResultSelector(("items",)),
|
|
251
312
|
cursor=CursorSpec(
|
|
252
313
|
parameter_path=ParameterPath(("LAST_ID",)),
|
|
@@ -261,6 +322,11 @@ stream = client.iter_list_cursor(
|
|
|
261
322
|
Cursor values must be unique and strictly monotonic. If an endpoint exposes only a non-unique
|
|
262
323
|
boundary, use an application-owned direct-call workflow or supply a unique tie-breaker.
|
|
263
324
|
|
|
325
|
+
For multiple parent-bound cursor chains, `iter_cursors()` keeps cursor progress and correlation
|
|
326
|
+
isolated per binding while ready pages share the physical batch queue.
|
|
327
|
+
|
|
328
|
+

|
|
329
|
+
|
|
264
330
|
See [architecture](docs/architecture.md), [migration](docs/migration.md),
|
|
265
331
|
[performance](docs/performance.md), and [endpoint recipes](docs/recipes.md) for the complete
|
|
266
332
|
contracts and selection guidance.
|
|
@@ -271,8 +337,11 @@ contracts and selection guidance.
|
|
|
271
337
|
client remains unaware of entity types: a binding can represent a deal, lead, chat or any other
|
|
272
338
|
caller-defined parent.
|
|
273
339
|
|
|
340
|
+

|
|
341
|
+
|
|
274
342
|
<!-- tested: tests/client_v2_test.py::test_bound_references_apply_nested_updates_off_wire_and_emit_exact_completion -->
|
|
275
343
|
```python
|
|
344
|
+
from b24api import RouteKind
|
|
276
345
|
from b24api import (
|
|
277
346
|
BatchDispatch,
|
|
278
347
|
Binding,
|
|
@@ -293,7 +362,7 @@ bindings = (
|
|
|
293
362
|
)
|
|
294
363
|
|
|
295
364
|
stream = client.iter_references(
|
|
296
|
-
Request("example.comment.list", replay_safety=ReplaySafety.SAFE),
|
|
365
|
+
Request("example.comment.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
297
366
|
bindings,
|
|
298
367
|
traversal=SequentialTraversal(selector=ResultSelector(("items",)), identity=identity),
|
|
299
368
|
dispatch=BatchDispatch(batch_size=25, concurrency=2),
|
|
@@ -313,6 +382,7 @@ under different parents are not conflated.
|
|
|
313
382
|
|
|
314
383
|
<!-- tested: tests/client_v2_test.py::test_bound_references_apply_nested_updates_off_wire_and_emit_exact_completion -->
|
|
315
384
|
```python
|
|
385
|
+
from b24api import RouteKind
|
|
316
386
|
from b24api import (
|
|
317
387
|
Binding,
|
|
318
388
|
CursorSpec,
|
|
@@ -334,7 +404,7 @@ chat_bindings = (
|
|
|
334
404
|
)
|
|
335
405
|
|
|
336
406
|
messages = client.iter_references(
|
|
337
|
-
Request("example.message.list", replay_safety=ReplaySafety.SAFE),
|
|
407
|
+
Request("example.message.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
338
408
|
chat_bindings,
|
|
339
409
|
traversal=CursorTraversal(
|
|
340
410
|
selector=ResultSelector(("items",)),
|
|
@@ -377,13 +447,19 @@ and publish the same final report where the Python exception type permits it.
|
|
|
377
447
|
## Resource boundaries
|
|
378
448
|
|
|
379
449
|
`ExecutionPolicy` bounds requests, pages, elapsed time, attempts, decompressed response bytes,
|
|
380
|
-
buffered commands and rows, direct concurrency and active
|
|
381
|
-
is 16 MiB and is enforced while streaming, before JSON
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
450
|
+
buffered commands and rows, retained unordered identity keys, direct concurrency and active
|
|
451
|
+
references. The default response ceiling is 16 MiB and is enforced while streaming, before JSON
|
|
452
|
+
decoding.
|
|
453
|
+
|
|
454
|
+
Sequential, counted, and multi-reference exact traversal retain at most `max_identity_keys`
|
|
455
|
+
observed identities per operation in memory (100,000 by default). All active reference bindings
|
|
456
|
+
share that ceiling. A page that would exceed it is rejected atomically with typed budget evidence.
|
|
457
|
+
Set a larger finite ceiling when the expected aggregate cardinality is known, or pass
|
|
458
|
+
`identity_store=` to `iter_list`/`iter_list_counted` so a caller-owned `IdentityStore` (for example a
|
|
459
|
+
SQLite table keyed by `identity_store_key(...)`) proves uniqueness while in-process identity memory
|
|
460
|
+
stays bounded by one page; the client never closes that store. Repeated-page detection still keeps
|
|
461
|
+
one short fingerprint per page, so raise `max_pages` deliberately for very long traversals.
|
|
462
|
+
Strict keyset and cursor traversal retain only monotonic progression state when sufficient.
|
|
387
463
|
|
|
388
464
|
## CLI
|
|
389
465
|
|
|
@@ -392,10 +468,10 @@ errors go to stderr. Credentials come only from `Settings` and cannot be passed
|
|
|
392
468
|
|
|
393
469
|
<!-- tested-console: tests/cli_test.py::test_call_routes_replay_safety_and_keeps_success_data_on_stdout -->
|
|
394
470
|
```console
|
|
395
|
-
b24api call profile
|
|
396
|
-
b24api call example.item.get --params '{"id":7}' --raw --replay-safety safe
|
|
397
|
-
b24api list example.item.list --params @params.json
|
|
398
|
-
b24api list example.item.list --strategy counted --contract @counted-contract.json
|
|
471
|
+
b24api call profile --route bare
|
|
472
|
+
b24api call example.item.get --route bare --params '{"id":7}' --raw --replay-safety safe
|
|
473
|
+
b24api list example.item.list --route bare --params @params.json
|
|
474
|
+
b24api list example.item.list --route bare --strategy counted --contract @counted-contract.json
|
|
399
475
|
```
|
|
400
476
|
|
|
401
477
|
The `--raw` CLI option selects the response envelope; it does not alter the Python API. Advanced
|