b24api 2.2.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.2.0 → b24api-2.3.0}/PKG-INFO +66 -31
- b24api-2.2.0/b24api.egg-info/PKG-INFO → b24api-2.3.0/README.md +64 -41
- {b24api-2.2.0 → b24api-2.3.0}/b24api/__init__.py +82 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/_client_traversal.py +15 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/_stream.py +28 -77
- {b24api-2.2.0 → b24api-2.3.0}/b24api/batch/engine.py +69 -12
- {b24api-2.2.0 → b24api-2.3.0}/b24api/batch/logical.py +90 -5
- {b24api-2.2.0 → b24api-2.3.0}/b24api/batch/stream.py +2 -1
- {b24api-2.2.0 → b24api-2.3.0}/b24api/cli.py +8 -4
- {b24api-2.2.0 → b24api-2.3.0}/b24api/cli_contract.py +10 -2
- {b24api-2.2.0 → b24api-2.3.0}/b24api/client.py +10 -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.2.0 → b24api-2.3.0}/b24api/contracts/__init__.py +78 -0
- b24api-2.3.0/b24api/contracts/bounded_range.py +81 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/contracts/command.py +13 -0
- b24api-2.3.0/b24api/contracts/completion.py +158 -0
- b24api-2.3.0/b24api/contracts/identity_store.py +45 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/contracts/keyset_capability.py +1 -1
- b24api-2.3.0/b24api/contracts/page_stop.py +68 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/contracts/policy.py +2 -0
- b24api-2.3.0/b24api/contracts/positional.py +236 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/contracts/reference.py +16 -1
- {b24api-2.2.0 → b24api-2.3.0}/b24api/contracts/report.py +16 -41
- {b24api-2.2.0 → b24api-2.3.0}/b24api/contracts/request.py +62 -10
- {b24api-2.2.0 → b24api-2.3.0}/b24api/contracts/traversal.py +142 -1
- b24api-2.3.0/b24api/contracts/violation.py +102 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/errors.py +43 -3
- {b24api-2.2.0 → b24api-2.3.0}/b24api/execution/__init__.py +14 -1
- {b24api-2.2.0 → b24api-2.3.0}/b24api/execution/context.py +19 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/execution/executor.py +57 -69
- {b24api-2.2.0 → b24api-2.3.0}/b24api/execution/failure.py +26 -9
- {b24api-2.2.0 → b24api-2.3.0}/b24api/execution/rate.py +134 -18
- {b24api-2.2.0 → b24api-2.3.0}/b24api/execution/snapshot.py +5 -0
- b24api-2.3.0/b24api/execution/throttle.py +53 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/redaction.py +1 -1
- {b24api-2.2.0 → b24api-2.3.0}/b24api/references/binding.py +5 -2
- {b24api-2.2.0 → b24api-2.3.0}/b24api/references/dispatch.py +8 -2
- b24api-2.3.0/b24api/references/dispatch_plan.py +27 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/references/facade.py +51 -60
- {b24api-2.2.0 → b24api-2.3.0}/b24api/references/fanout.py +4 -4
- {b24api-2.2.0 → b24api-2.3.0}/b24api/references/scheduler.py +87 -41
- {b24api-2.2.0 → b24api-2.3.0}/b24api/references/stream.py +44 -26
- {b24api-2.2.0 → b24api-2.3.0}/b24api/references/support.py +67 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/testing/__init__.py +3 -0
- b24api-2.3.0/b24api/testing/scripted.py +147 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/testing/transport.py +39 -8
- {b24api-2.2.0 → b24api-2.3.0}/b24api/transport/base.py +58 -4
- {b24api-2.2.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.2.0 → b24api-2.3.0}/b24api/traversal/counted.py +60 -4
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/counted_batch.py +44 -16
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/cursor.py +13 -3
- b24api-2.3.0/b24api/traversal/cursor_domain.py +61 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/driver.py +61 -86
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/facade.py +50 -43
- b24api-2.3.0/b24api/traversal/facade_support.py +36 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/identity.py +71 -26
- b24api-2.3.0/b24api/traversal/identity_ledger.py +59 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/keyset.py +6 -2
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/keyset_eligibility.py +50 -13
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/keyset_fast_stream.py +42 -5
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/keyset_observation.py +2 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/keyset_scheduler.py +43 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/keyset_step.py +46 -3
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/keyset_transactions.py +36 -3
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/keyset_verifier.py +33 -3
- b24api-2.3.0/b24api/traversal/offset_rules.py +76 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/ordered_admission.py +2 -3
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/page_validation.py +20 -3
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/plans.py +36 -3
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/sequential.py +65 -12
- b24api-2.3.0/b24api/traversal/sparse.py +55 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/stream.py +90 -3
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/values.py +5 -0
- b24api-2.2.0/README.md → b24api-2.3.0/b24api.egg-info/PKG-INFO +76 -29
- {b24api-2.2.0 → b24api-2.3.0}/b24api.egg-info/SOURCES.txt +22 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api.egg-info/requires.txt +1 -1
- {b24api-2.2.0 → b24api-2.3.0}/pyproject.toml +3 -2
- b24api-2.2.0/b24api/transport/protocol.py +0 -157
- {b24api-2.2.0 → b24api-2.3.0}/LICENSE +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/MANIFEST.in +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/_audit.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/_error_types.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/batch/__init__.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/batch/facade.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/batch/outcome.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/contracts/dispatch.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/contracts/json.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/contracts/keyset_execution.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/contracts/page.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/contracts/response.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/contracts/stream.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/contracts/wire.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/encoding.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/execution/cleanup.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/references/__init__.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/references/outcome.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/settings.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/testing/_isolation.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/transport/__init__.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/__init__.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/keyset_auto.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/keyset_capability.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/keyset_costs.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/keyset_fast_plan.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/keyset_partition.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/keyset_range.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/keyset_reporting.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/keyset_transaction_contract.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api/traversal/page_adaptation.py +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api.egg-info/dependency_links.txt +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api.egg-info/entry_points.txt +0 -0
- {b24api-2.2.0 → b24api-2.3.0}/b24api.egg-info/top_level.txt +0 -0
- {b24api-2.2.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
|
|
@@ -166,6 +169,7 @@ control this strategy's completion. Exact database implementation is endpoint-sp
|
|
|
166
169
|
|
|
167
170
|
<!-- tested: tests/client_v2_test.py::test_iter_list_is_sequential_mechanics_only_and_report_is_post_cleanup -->
|
|
168
171
|
```python
|
|
172
|
+
from b24api import RouteKind
|
|
169
173
|
from b24api import IdentityCoercion, IdentitySpec, ResultSelector
|
|
170
174
|
|
|
171
175
|
identity = IdentitySpec(
|
|
@@ -176,7 +180,7 @@ identity = IdentitySpec(
|
|
|
176
180
|
)
|
|
177
181
|
|
|
178
182
|
stream = client.iter_list(
|
|
179
|
-
Request("example.item.list", replay_safety=ReplaySafety.SAFE),
|
|
183
|
+
Request("example.item.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
180
184
|
selector=ResultSelector(("items",)),
|
|
181
185
|
identity=identity,
|
|
182
186
|
)
|
|
@@ -194,10 +198,11 @@ sequence and records that degradation in the operation report.
|
|
|
194
198
|
|
|
195
199
|
<!-- tested: tests/client_findings_3_test.py::test_shape_rejection_is_retained_as_zero_admission_page_evidence -->
|
|
196
200
|
```python
|
|
201
|
+
from b24api import RouteKind
|
|
197
202
|
from b24api import ResultCollectionShape
|
|
198
203
|
|
|
199
204
|
stream = client.iter_list(
|
|
200
|
-
Request("example.dictionary.list", replay_safety=ReplaySafety.SAFE),
|
|
205
|
+
Request("example.dictionary.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
201
206
|
selector=ResultSelector(("items",)),
|
|
202
207
|
collection_shape=ResultCollectionShape.MAPPING_VALUES,
|
|
203
208
|
)
|
|
@@ -214,8 +219,9 @@ bounded physical batches.
|
|
|
214
219
|
|
|
215
220
|
<!-- tested: tests/client_v2_test.py::test_counted_traversal_preserves_frozen_request_shape_and_exact_identity -->
|
|
216
221
|
```python
|
|
222
|
+
from b24api import RouteKind
|
|
217
223
|
stream = client.iter_list_counted(
|
|
218
|
-
Request("example.item.list", replay_safety=ReplaySafety.SAFE),
|
|
224
|
+
Request("example.item.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
219
225
|
selector=ResultSelector(("items",)),
|
|
220
226
|
identity=identity,
|
|
221
227
|
page_size=50,
|
|
@@ -257,6 +263,9 @@ representative fixture. It performs five strict-bound checks and returns only a
|
|
|
257
263
|
unsupported and inconclusive verdicts raise `KeysetCapabilityError`. The ordinary
|
|
258
264
|
`iter_list_keyset()` remains a separate caller-asserted operation with zero verifier canaries and
|
|
259
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.
|
|
260
269
|
|
|
261
270
|
Every list operation also accepts an immutable `PageAdapter` strategy. The adapter synchronously
|
|
262
271
|
maps selected frozen items using sibling result metadata while preserving cardinality, order and
|
|
@@ -269,16 +278,33 @@ record the selected strategy and reason: unbounded auto continuation has the sam
|
|
|
269
278
|
|
|
270
279
|
<!-- tested: tests/keyset_fast_test.py::test_omitted_execution_defaults_to_auto -->
|
|
271
280
|
```python
|
|
272
|
-
|
|
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
|
+
)
|
|
273
301
|
|
|
274
302
|
stream = client.iter_list_keyset(
|
|
275
|
-
|
|
276
|
-
selector=
|
|
303
|
+
request,
|
|
304
|
+
selector=selector,
|
|
277
305
|
identity=identity,
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
order_path=ParameterPath(("order",)),
|
|
281
|
-
),
|
|
306
|
+
page_size=50,
|
|
307
|
+
keyset=keyset,
|
|
282
308
|
)
|
|
283
309
|
```
|
|
284
310
|
|
|
@@ -289,10 +315,11 @@ message-list methods.
|
|
|
289
315
|
|
|
290
316
|
<!-- tested: tests/client_v2_test.py::test_keyset_and_cursor_are_explicit_strict_alternatives -->
|
|
291
317
|
```python
|
|
318
|
+
from b24api import RouteKind
|
|
292
319
|
from b24api import CursorSpec, ParameterPath
|
|
293
320
|
|
|
294
321
|
stream = client.iter_list_cursor(
|
|
295
|
-
Request("example.message.list", replay_safety=ReplaySafety.SAFE),
|
|
322
|
+
Request("example.message.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
296
323
|
selector=ResultSelector(("items",)),
|
|
297
324
|
cursor=CursorSpec(
|
|
298
325
|
parameter_path=ParameterPath(("LAST_ID",)),
|
|
@@ -326,6 +353,7 @@ caller-defined parent.
|
|
|
326
353
|
|
|
327
354
|
<!-- tested: tests/client_v2_test.py::test_bound_references_apply_nested_updates_off_wire_and_emit_exact_completion -->
|
|
328
355
|
```python
|
|
356
|
+
from b24api import RouteKind
|
|
329
357
|
from b24api import (
|
|
330
358
|
BatchDispatch,
|
|
331
359
|
Binding,
|
|
@@ -346,7 +374,7 @@ bindings = (
|
|
|
346
374
|
)
|
|
347
375
|
|
|
348
376
|
stream = client.iter_references(
|
|
349
|
-
Request("example.comment.list", replay_safety=ReplaySafety.SAFE),
|
|
377
|
+
Request("example.comment.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
350
378
|
bindings,
|
|
351
379
|
traversal=SequentialTraversal(selector=ResultSelector(("items",)), identity=identity),
|
|
352
380
|
dispatch=BatchDispatch(batch_size=25, concurrency=2),
|
|
@@ -366,6 +394,7 @@ under different parents are not conflated.
|
|
|
366
394
|
|
|
367
395
|
<!-- tested: tests/client_v2_test.py::test_bound_references_apply_nested_updates_off_wire_and_emit_exact_completion -->
|
|
368
396
|
```python
|
|
397
|
+
from b24api import RouteKind
|
|
369
398
|
from b24api import (
|
|
370
399
|
Binding,
|
|
371
400
|
CursorSpec,
|
|
@@ -387,7 +416,7 @@ chat_bindings = (
|
|
|
387
416
|
)
|
|
388
417
|
|
|
389
418
|
messages = client.iter_references(
|
|
390
|
-
Request("example.message.list", replay_safety=ReplaySafety.SAFE),
|
|
419
|
+
Request("example.message.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
391
420
|
chat_bindings,
|
|
392
421
|
traversal=CursorTraversal(
|
|
393
422
|
selector=ResultSelector(("items",)),
|
|
@@ -430,13 +459,19 @@ and publish the same final report where the Python exception type permits it.
|
|
|
430
459
|
## Resource boundaries
|
|
431
460
|
|
|
432
461
|
`ExecutionPolicy` bounds requests, pages, elapsed time, attempts, decompressed response bytes,
|
|
433
|
-
buffered commands and rows, direct concurrency and active
|
|
434
|
-
is 16 MiB and is enforced while streaming, before JSON
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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.
|
|
440
475
|
|
|
441
476
|
## CLI
|
|
442
477
|
|
|
@@ -445,10 +480,10 @@ errors go to stderr. Credentials come only from `Settings` and cannot be passed
|
|
|
445
480
|
|
|
446
481
|
<!-- tested-console: tests/cli_test.py::test_call_routes_replay_safety_and_keeps_success_data_on_stdout -->
|
|
447
482
|
```console
|
|
448
|
-
b24api call profile
|
|
449
|
-
b24api call example.item.get --params '{"id":7}' --raw --replay-safety safe
|
|
450
|
-
b24api list example.item.list --params @params.json
|
|
451
|
-
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
|
|
452
487
|
```
|
|
453
488
|
|
|
454
489
|
The `--raw` CLI option selects the response envelope; it does not alter the Python API. Advanced
|
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: b24api
|
|
3
|
-
Version: 2.2.0
|
|
4
|
-
Summary: Bitrix24 API
|
|
5
|
-
Requires-Python: >=3.12
|
|
6
|
-
Description-Content-Type: text/markdown
|
|
7
|
-
License-File: LICENSE
|
|
8
|
-
Requires-Dist: httpx[http2]>=0.28.1
|
|
9
|
-
Requires-Dist: pydantic>=2.11.7
|
|
10
|
-
Requires-Dist: pydantic-settings>=2.10.1
|
|
11
|
-
Dynamic: license-file
|
|
12
|
-
|
|
13
1
|
# b24api 2.x
|
|
14
2
|
|
|
15
3
|
`b24api` is a thin asynchronous Bitrix24 REST client for Python 3.12+. It knows how to send
|
|
@@ -29,10 +17,10 @@ work so its HTTP/2 connection pool and rate state are reused.
|
|
|
29
17
|
|
|
30
18
|
<!-- tested: tests/client_v2_test.py::test_call_and_call_response_have_stable_detached_types -->
|
|
31
19
|
```python
|
|
32
|
-
from b24api import Bitrix24, Request
|
|
20
|
+
from b24api import Bitrix24, Request, RouteKind
|
|
33
21
|
|
|
34
22
|
async with Bitrix24() as client:
|
|
35
|
-
profile = await client.call(Request("profile"))
|
|
23
|
+
profile = await client.call(Request("profile", route=RouteKind.BARE))
|
|
36
24
|
```
|
|
37
25
|
|
|
38
26
|
The client owns its default transport. An injected transport remains caller-owned. `aclose()` is
|
|
@@ -48,15 +36,17 @@ The operation is explicit and never hides malformed JSON by falling back to byte
|
|
|
48
36
|
|
|
49
37
|
<!-- tested: tests/client_findings_3_test.py::test_binary_call_returns_every_success_byte_without_json_sniffing -->
|
|
50
38
|
```python
|
|
51
|
-
|
|
39
|
+
from b24api import RouteKind
|
|
40
|
+
archive = await client.call_bytes(Request("example.export.download", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE))
|
|
52
41
|
payload = archive.body
|
|
53
42
|
```
|
|
54
43
|
|
|
55
44
|
<!-- tested: tests/client_v2_test.py::test_call_and_call_response_have_stable_detached_types -->
|
|
56
45
|
```python
|
|
46
|
+
from b24api import RouteKind
|
|
57
47
|
from b24api import ReplaySafety
|
|
58
48
|
|
|
59
|
-
request = Request("example.item.get", {"id": 7}, ReplaySafety.SAFE)
|
|
49
|
+
request = Request("example.item.get", {"id": 7}, ReplaySafety.SAFE, route=RouteKind.BARE)
|
|
60
50
|
decoded = await client.call(request)
|
|
61
51
|
response = await client.call_response(request)
|
|
62
52
|
```
|
|
@@ -97,11 +87,12 @@ matching a result to the object, file, chat or database row that produced its re
|
|
|
97
87
|
|
|
98
88
|
<!-- tested: tests/client_v2_test.py::test_logical_batch_is_unbounded_ordered_and_correlation_is_strictly_off_wire -->
|
|
99
89
|
```python
|
|
90
|
+
from b24api import RouteKind
|
|
100
91
|
from b24api import Command, CommandSuccess
|
|
101
92
|
|
|
102
93
|
commands = (
|
|
103
94
|
Command(
|
|
104
|
-
Request("example.item.get", {"id": item_id}, ReplaySafety.SAFE),
|
|
95
|
+
Request("example.item.get", {"id": item_id}, ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
105
96
|
correlation=item_id,
|
|
106
97
|
)
|
|
107
98
|
for item_id in source_ids
|
|
@@ -166,6 +157,7 @@ control this strategy's completion. Exact database implementation is endpoint-sp
|
|
|
166
157
|
|
|
167
158
|
<!-- tested: tests/client_v2_test.py::test_iter_list_is_sequential_mechanics_only_and_report_is_post_cleanup -->
|
|
168
159
|
```python
|
|
160
|
+
from b24api import RouteKind
|
|
169
161
|
from b24api import IdentityCoercion, IdentitySpec, ResultSelector
|
|
170
162
|
|
|
171
163
|
identity = IdentitySpec(
|
|
@@ -176,7 +168,7 @@ identity = IdentitySpec(
|
|
|
176
168
|
)
|
|
177
169
|
|
|
178
170
|
stream = client.iter_list(
|
|
179
|
-
Request("example.item.list", replay_safety=ReplaySafety.SAFE),
|
|
171
|
+
Request("example.item.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
180
172
|
selector=ResultSelector(("items",)),
|
|
181
173
|
identity=identity,
|
|
182
174
|
)
|
|
@@ -194,10 +186,11 @@ sequence and records that degradation in the operation report.
|
|
|
194
186
|
|
|
195
187
|
<!-- tested: tests/client_findings_3_test.py::test_shape_rejection_is_retained_as_zero_admission_page_evidence -->
|
|
196
188
|
```python
|
|
189
|
+
from b24api import RouteKind
|
|
197
190
|
from b24api import ResultCollectionShape
|
|
198
191
|
|
|
199
192
|
stream = client.iter_list(
|
|
200
|
-
Request("example.dictionary.list", replay_safety=ReplaySafety.SAFE),
|
|
193
|
+
Request("example.dictionary.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
201
194
|
selector=ResultSelector(("items",)),
|
|
202
195
|
collection_shape=ResultCollectionShape.MAPPING_VALUES,
|
|
203
196
|
)
|
|
@@ -214,8 +207,9 @@ bounded physical batches.
|
|
|
214
207
|
|
|
215
208
|
<!-- tested: tests/client_v2_test.py::test_counted_traversal_preserves_frozen_request_shape_and_exact_identity -->
|
|
216
209
|
```python
|
|
210
|
+
from b24api import RouteKind
|
|
217
211
|
stream = client.iter_list_counted(
|
|
218
|
-
Request("example.item.list", replay_safety=ReplaySafety.SAFE),
|
|
212
|
+
Request("example.item.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
219
213
|
selector=ResultSelector(("items",)),
|
|
220
214
|
identity=identity,
|
|
221
215
|
page_size=50,
|
|
@@ -257,6 +251,9 @@ representative fixture. It performs five strict-bound checks and returns only a
|
|
|
257
251
|
unsupported and inconclusive verdicts raise `KeysetCapabilityError`. The ordinary
|
|
258
252
|
`iter_list_keyset()` remains a separate caller-asserted operation with zero verifier canaries and
|
|
259
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.
|
|
260
257
|
|
|
261
258
|
Every list operation also accepts an immutable `PageAdapter` strategy. The adapter synchronously
|
|
262
259
|
maps selected frozen items using sibling result metadata while preserving cardinality, order and
|
|
@@ -269,16 +266,33 @@ record the selected strategy and reason: unbounded auto continuation has the sam
|
|
|
269
266
|
|
|
270
267
|
<!-- tested: tests/keyset_fast_test.py::test_omitted_execution_defaults_to_auto -->
|
|
271
268
|
```python
|
|
272
|
-
|
|
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
|
+
)
|
|
273
289
|
|
|
274
290
|
stream = client.iter_list_keyset(
|
|
275
|
-
|
|
276
|
-
selector=
|
|
291
|
+
request,
|
|
292
|
+
selector=selector,
|
|
277
293
|
identity=identity,
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
order_path=ParameterPath(("order",)),
|
|
281
|
-
),
|
|
294
|
+
page_size=50,
|
|
295
|
+
keyset=keyset,
|
|
282
296
|
)
|
|
283
297
|
```
|
|
284
298
|
|
|
@@ -289,10 +303,11 @@ message-list methods.
|
|
|
289
303
|
|
|
290
304
|
<!-- tested: tests/client_v2_test.py::test_keyset_and_cursor_are_explicit_strict_alternatives -->
|
|
291
305
|
```python
|
|
306
|
+
from b24api import RouteKind
|
|
292
307
|
from b24api import CursorSpec, ParameterPath
|
|
293
308
|
|
|
294
309
|
stream = client.iter_list_cursor(
|
|
295
|
-
Request("example.message.list", replay_safety=ReplaySafety.SAFE),
|
|
310
|
+
Request("example.message.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
296
311
|
selector=ResultSelector(("items",)),
|
|
297
312
|
cursor=CursorSpec(
|
|
298
313
|
parameter_path=ParameterPath(("LAST_ID",)),
|
|
@@ -326,6 +341,7 @@ caller-defined parent.
|
|
|
326
341
|
|
|
327
342
|
<!-- tested: tests/client_v2_test.py::test_bound_references_apply_nested_updates_off_wire_and_emit_exact_completion -->
|
|
328
343
|
```python
|
|
344
|
+
from b24api import RouteKind
|
|
329
345
|
from b24api import (
|
|
330
346
|
BatchDispatch,
|
|
331
347
|
Binding,
|
|
@@ -346,7 +362,7 @@ bindings = (
|
|
|
346
362
|
)
|
|
347
363
|
|
|
348
364
|
stream = client.iter_references(
|
|
349
|
-
Request("example.comment.list", replay_safety=ReplaySafety.SAFE),
|
|
365
|
+
Request("example.comment.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
350
366
|
bindings,
|
|
351
367
|
traversal=SequentialTraversal(selector=ResultSelector(("items",)), identity=identity),
|
|
352
368
|
dispatch=BatchDispatch(batch_size=25, concurrency=2),
|
|
@@ -366,6 +382,7 @@ under different parents are not conflated.
|
|
|
366
382
|
|
|
367
383
|
<!-- tested: tests/client_v2_test.py::test_bound_references_apply_nested_updates_off_wire_and_emit_exact_completion -->
|
|
368
384
|
```python
|
|
385
|
+
from b24api import RouteKind
|
|
369
386
|
from b24api import (
|
|
370
387
|
Binding,
|
|
371
388
|
CursorSpec,
|
|
@@ -387,7 +404,7 @@ chat_bindings = (
|
|
|
387
404
|
)
|
|
388
405
|
|
|
389
406
|
messages = client.iter_references(
|
|
390
|
-
Request("example.message.list", replay_safety=ReplaySafety.SAFE),
|
|
407
|
+
Request("example.message.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
391
408
|
chat_bindings,
|
|
392
409
|
traversal=CursorTraversal(
|
|
393
410
|
selector=ResultSelector(("items",)),
|
|
@@ -430,13 +447,19 @@ and publish the same final report where the Python exception type permits it.
|
|
|
430
447
|
## Resource boundaries
|
|
431
448
|
|
|
432
449
|
`ExecutionPolicy` bounds requests, pages, elapsed time, attempts, decompressed response bytes,
|
|
433
|
-
buffered commands and rows, direct concurrency and active
|
|
434
|
-
is 16 MiB and is enforced while streaming, before JSON
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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.
|
|
440
463
|
|
|
441
464
|
## CLI
|
|
442
465
|
|
|
@@ -445,10 +468,10 @@ errors go to stderr. Credentials come only from `Settings` and cannot be passed
|
|
|
445
468
|
|
|
446
469
|
<!-- tested-console: tests/cli_test.py::test_call_routes_replay_safety_and_keeps_success_data_on_stdout -->
|
|
447
470
|
```console
|
|
448
|
-
b24api call profile
|
|
449
|
-
b24api call example.item.get --params '{"id":7}' --raw --replay-safety safe
|
|
450
|
-
b24api list example.item.list --params @params.json
|
|
451
|
-
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
|
|
452
475
|
```
|
|
453
476
|
|
|
454
477
|
The `--raw` CLI option selects the response envelope; it does not alter the Python API. Advanced
|