b24api 2.3.0__tar.gz → 2.4.2__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.3.0 → b24api-2.4.2}/PKG-INFO +120 -37
- b24api-2.3.0/b24api.egg-info/PKG-INFO → b24api-2.4.2/README.md +99 -47
- b24api-2.4.2/b24api/__init__.py +134 -0
- b24api-2.4.2/b24api/_diagnostics.py +138 -0
- b24api-2.4.2/b24api/_sources.py +172 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/batch/engine.py +190 -44
- {b24api-2.3.0 → b24api-2.4.2}/b24api/batch/facade.py +11 -12
- b24api-2.4.2/b24api/batch/logical.py +396 -0
- b24api-2.4.2/b24api/batch/stream.py +272 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/cli.py +3 -1
- {b24api-2.3.0 → b24api-2.4.2}/b24api/cli_contract.py +12 -7
- {b24api-2.3.0 → b24api-2.4.2}/b24api/client.py +40 -15
- {b24api-2.3.0 → b24api-2.4.2}/b24api/completion/fast_recorder.py +7 -18
- {b24api-2.3.0 → b24api-2.4.2}/b24api/completion/gate.py +257 -158
- b24api-2.4.2/b24api/completion/operation_stream.py +294 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/completion/recorder.py +4 -13
- {b24api-2.3.0 → b24api-2.4.2}/b24api/completion/reference_recorder.py +3 -11
- {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/__init__.py +3 -2
- {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/command.py +3 -3
- {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/completion.py +16 -1
- {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/dispatch.py +5 -4
- b24api-2.4.2/b24api/contracts/error_base.py +75 -0
- b24api-2.4.2/b24api/contracts/evidence.py +41 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/keyset_execution.py +4 -2
- {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/policy.py +29 -11
- {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/positional.py +24 -6
- {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/reference.py +1 -1
- {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/report.py +39 -15
- {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/request.py +75 -53
- b24api-2.4.2/b24api/contracts/request_summary.py +63 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/response.py +6 -33
- b24api-2.4.2/b24api/contracts/v3_codes.py +61 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/violation.py +3 -7
- {b24api-2.3.0 → b24api-2.4.2}/b24api/errors.py +57 -90
- b24api-2.4.2/b24api/execution/boundary.py +80 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/execution/cleanup.py +15 -12
- {b24api-2.3.0 → b24api-2.4.2}/b24api/execution/context.py +90 -70
- {b24api-2.3.0 → b24api-2.4.2}/b24api/execution/executor.py +238 -145
- {b24api-2.3.0 → b24api-2.4.2}/b24api/execution/failure.py +62 -3
- b24api-2.4.2/b24api/execution/lifecycle.py +400 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/execution/rate.py +93 -88
- {b24api-2.3.0 → b24api-2.4.2}/b24api/execution/snapshot.py +8 -4
- b24api-2.4.2/b24api/execution/throttle.py +107 -0
- b24api-2.4.2/b24api/migration.py +260 -0
- b24api-2.4.2/b24api/py.typed +0 -0
- b24api-2.4.2/b24api/redaction.py +339 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/references/binding.py +27 -114
- {b24api-2.3.0 → b24api-2.4.2}/b24api/references/dispatch.py +34 -31
- {b24api-2.3.0 → b24api-2.4.2}/b24api/references/dispatch_plan.py +2 -3
- {b24api-2.3.0 → b24api-2.4.2}/b24api/references/facade.py +17 -11
- {b24api-2.3.0 → b24api-2.4.2}/b24api/references/fanout.py +35 -82
- {b24api-2.3.0 → b24api-2.4.2}/b24api/references/outcome.py +4 -7
- {b24api-2.3.0 → b24api-2.4.2}/b24api/references/scheduler.py +211 -231
- b24api-2.4.2/b24api/references/stream.py +274 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/references/support.py +4 -97
- {b24api-2.3.0 → b24api-2.4.2}/b24api/testing/scripted.py +4 -4
- {b24api-2.3.0 → b24api-2.4.2}/b24api/testing/transport.py +2 -1
- {b24api-2.3.0 → b24api-2.4.2}/b24api/transport/base.py +2 -1
- b24api-2.4.2/b24api/transport/decoding.py +207 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/transport/httpx.py +48 -40
- b24api-2.4.2/b24api/transport/log_records.py +111 -0
- b24api-2.4.2/b24api/transport/logging_shield.py +366 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/transport/protocol.py +81 -54
- {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/control_preflight.py +20 -7
- {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/counted.py +10 -5
- b24api-2.4.2/b24api/traversal/counted_batch.py +369 -0
- b24api-2.4.2/b24api/traversal/counted_rules.py +74 -0
- b24api-2.4.2/b24api/traversal/cursor.py +106 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/driver.py +148 -42
- {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/facade.py +31 -21
- {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/identity.py +7 -76
- b24api-2.4.2/b24api/traversal/keyset.py +67 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/keyset_auto.py +11 -20
- {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/keyset_capability.py +8 -107
- {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/keyset_eligibility.py +12 -8
- {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/keyset_fast_plan.py +1 -39
- b24api-2.4.2/b24api/traversal/keyset_fast_stream.py +174 -0
- b24api-2.4.2/b24api/traversal/keyset_geometry.py +313 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/keyset_observation.py +125 -85
- b24api-2.3.0/b24api/traversal/ordered_admission.py → b24api-2.4.2/b24api/traversal/keyset_ordered_admission.py +2 -10
- b24api-2.3.0/b24api/traversal/page_validation.py → b24api-2.4.2/b24api/traversal/keyset_page_validation.py +3 -9
- b24api-2.4.2/b24api/traversal/keyset_plan.py +453 -0
- b24api-2.4.2/b24api/traversal/keyset_reporting.py +96 -0
- b24api-2.4.2/b24api/traversal/keyset_runtime.py +413 -0
- b24api-2.4.2/b24api/traversal/keyset_transaction_contract.py +195 -0
- b24api-2.4.2/b24api/traversal/keyset_transactions.py +451 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/keyset_verifier.py +35 -13
- b24api-2.4.2/b24api/traversal/offset_rules.py +159 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/page_adaptation.py +3 -2
- {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/plans.py +8 -8
- b24api-2.4.2/b24api/traversal/sequential.py +232 -0
- b24api-2.4.2/b24api/traversal/strategy_context.py +181 -0
- b24api-2.4.2/b24api/traversal/stream.py +299 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/values.py +4 -4
- b24api-2.3.0/README.md → b24api-2.4.2/b24api.egg-info/PKG-INFO +130 -35
- {b24api-2.3.0 → b24api-2.4.2}/b24api.egg-info/SOURCES.txt +20 -8
- {b24api-2.3.0 → b24api-2.4.2}/b24api.egg-info/requires.txt +3 -1
- b24api-2.4.2/pyproject.toml +132 -0
- b24api-2.3.0/b24api/__init__.py +0 -335
- b24api-2.3.0/b24api/_audit.py +0 -78
- b24api-2.3.0/b24api/_stream.py +0 -274
- b24api-2.3.0/b24api/batch/logical.py +0 -433
- b24api-2.3.0/b24api/batch/stream.py +0 -436
- b24api-2.3.0/b24api/execution/throttle.py +0 -53
- b24api-2.3.0/b24api/redaction.py +0 -205
- b24api-2.3.0/b24api/references/stream.py +0 -394
- b24api-2.3.0/b24api/transport/logging_shield.py +0 -244
- b24api-2.3.0/b24api/traversal/counted_batch.py +0 -288
- b24api-2.3.0/b24api/traversal/cursor.py +0 -108
- b24api-2.3.0/b24api/traversal/keyset.py +0 -68
- b24api-2.3.0/b24api/traversal/keyset_costs.py +0 -185
- b24api-2.3.0/b24api/traversal/keyset_fast_stream.py +0 -311
- b24api-2.3.0/b24api/traversal/keyset_partition.py +0 -27
- b24api-2.3.0/b24api/traversal/keyset_range.py +0 -80
- b24api-2.3.0/b24api/traversal/keyset_reporting.py +0 -111
- b24api-2.3.0/b24api/traversal/keyset_scheduler.py +0 -669
- b24api-2.3.0/b24api/traversal/keyset_transaction_contract.py +0 -108
- b24api-2.3.0/b24api/traversal/keyset_transactions.py +0 -418
- b24api-2.3.0/b24api/traversal/offset_rules.py +0 -76
- b24api-2.3.0/b24api/traversal/sequential.py +0 -252
- b24api-2.3.0/b24api/traversal/stream.py +0 -412
- b24api-2.3.0/pyproject.toml +0 -94
- {b24api-2.3.0 → b24api-2.4.2}/LICENSE +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/MANIFEST.in +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/_client_traversal.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/_error_types.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/batch/__init__.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/batch/outcome.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/completion/__init__.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/completion/closure.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/bounded_range.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/identity_store.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/json.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/keyset_capability.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/page.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/page_stop.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/stream.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/traversal.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/wire.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/encoding.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/execution/__init__.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/references/__init__.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/settings.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/testing/__init__.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/testing/_isolation.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/transport/__init__.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/__init__.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/cursor_domain.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/facade_support.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/identity_ledger.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/keyset_step.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/sparse.py +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api.egg-info/dependency_links.txt +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api.egg-info/entry_points.txt +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/b24api.egg-info/top_level.txt +0 -0
- {b24api-2.3.0 → b24api-2.4.2}/setup.cfg +0 -0
|
@@ -1,16 +1,35 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: b24api
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.2
|
|
4
4
|
Summary: Bitrix24 API
|
|
5
|
+
Author-email: Shkarupa Alex <shkarupa.alex@gmail.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/shkarupa-alex/b24api
|
|
8
|
+
Project-URL: Repository, https://github.com/shkarupa-alex/b24api
|
|
9
|
+
Project-URL: Issues, https://github.com/shkarupa-alex/b24api/issues
|
|
10
|
+
Keywords: bitrix24,rest,api,client,async,httpx,batch,pagination
|
|
11
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
12
|
+
Classifier: Framework :: AsyncIO
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Classifier: Typing :: Typed
|
|
5
22
|
Requires-Python: >=3.12
|
|
6
23
|
Description-Content-Type: text/markdown
|
|
7
24
|
License-File: LICENSE
|
|
8
25
|
Requires-Dist: httpx[http2]<0.29,>=0.28.1
|
|
9
|
-
Requires-Dist:
|
|
26
|
+
Requires-Dist: h2<4.5,>=4.3.0
|
|
27
|
+
Requires-Dist: hpack<4.3,>=4.1.0
|
|
28
|
+
Requires-Dist: pydantic>=2.12.0
|
|
10
29
|
Requires-Dist: pydantic-settings>=2.10.1
|
|
11
30
|
Dynamic: license-file
|
|
12
31
|
|
|
13
|
-
# b24api
|
|
32
|
+
# b24api 3.x
|
|
14
33
|
|
|
15
34
|
`b24api` is a thin asynchronous Bitrix24 REST client for Python 3.12+. It knows how to send
|
|
16
35
|
requests, split logical batches, traverse lists, retry safely, preserve caller correlation and
|
|
@@ -27,16 +46,42 @@ export BITRIX24_API_WEBHOOK_URL='https://portal.example/rest/.../'
|
|
|
27
46
|
Keep the webhook out of source, logs and command arguments. Reuse one client for a related unit of
|
|
28
47
|
work so its HTTP/2 connection pool and rate state are reused.
|
|
29
48
|
|
|
30
|
-
|
|
49
|
+
## Quickstart
|
|
50
|
+
|
|
51
|
+
<!-- tested: tests/readme_test.py::test_quickstart_runs_exactly_against_a_scripted_portal -->
|
|
31
52
|
```python
|
|
32
|
-
|
|
53
|
+
import os
|
|
54
|
+
|
|
55
|
+
from b24api import Bitrix24, Request
|
|
33
56
|
|
|
34
|
-
async with Bitrix24() as client:
|
|
35
|
-
|
|
57
|
+
async with Bitrix24.from_webhook(os.environ["BITRIX24_API_WEBHOOK_URL"]) as client:
|
|
58
|
+
deals = client.iter_list(Request.bare("crm.deal.list", {"select": ["ID", "TITLE"]}))
|
|
59
|
+
async for deal in deals:
|
|
60
|
+
print(deal["ID"], deal["TITLE"])
|
|
61
|
+
print(deals.report.state)
|
|
36
62
|
```
|
|
37
63
|
|
|
38
|
-
|
|
39
|
-
|
|
64
|
+
Four things are at work:
|
|
65
|
+
|
|
66
|
+
- **The client.** `Bitrix24.from_webhook()` checks the URL and owns the connection pool it opens;
|
|
67
|
+
`async with` closes it. `Bitrix24()` reads the same URL from the environment.
|
|
68
|
+
- **The request.** `Request.bare()` names a REST method and its parameters and sends them to the
|
|
69
|
+
classic `/rest/` endpoint. The route is always explicit: `Request.v3()` targets the V3 API.
|
|
70
|
+
- **`iter_list()`.** It walks the list page by page and reads one more, empty, page to confirm
|
|
71
|
+
the end.
|
|
72
|
+
- **The report.** `deals.report` says how the traversal ended. `completed` means every page was
|
|
73
|
+
read; a traversal that stops early or fails records why.
|
|
74
|
+
|
|
75
|
+
A single call returns the decoded `result`:
|
|
76
|
+
|
|
77
|
+
<!-- tested: tests/readme_test.py::test_quickstart_runs_exactly_against_a_scripted_portal -->
|
|
78
|
+
```python
|
|
79
|
+
users = await client.call(Request.bare("user.get", {"ID": 1}))
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
The client owns the transport it creates. An injected transport remains caller-owned. `aclose()` is
|
|
83
|
+
idempotent and closes active streams before the owned transport. To prove that no row is missing or
|
|
84
|
+
repeated, give the traversal an identity (see [Choosing a list operation](#choosing-a-list-operation)).
|
|
40
85
|
|
|
41
86
|
## Direct calls
|
|
42
87
|
|
|
@@ -71,10 +116,17 @@ may already have reached Bitrix:
|
|
|
71
116
|
| Value | Meaning | After possible dispatch |
|
|
72
117
|
|---|---|---|
|
|
73
118
|
| `SAFE` | Repeating the request cannot create a second business effect. Typical reads and explicitly idempotent operations belong here. | Automatic retry is allowed within policy budgets. |
|
|
74
|
-
| `UNSAFE` | Repeating the request is known to risk a duplicate effect, for example creating an entity without an idempotency key. | No automatic replay; the caller receives an ambiguous-execution error and reconciles state. |
|
|
119
|
+
| `UNSAFE` | Repeating the request is known to risk a duplicate effect, for example creating an entity without an idempotency key. | No automatic replay; the caller receives an ambiguous-execution error and reconciles state. It is retried only when the failure proves it did not run. |
|
|
75
120
|
| `UNKNOWN` | The caller has not established whether replay is safe. This is the default. | Same conservative behavior as `UNSAFE`, while diagnostics preserve that safety was unknown rather than known unsafe. |
|
|
76
121
|
|
|
77
|
-
A failure
|
|
122
|
+
A failure that proves the request did not run is retried whatever its safety: a transport failure
|
|
123
|
+
before dispatch, or a refusal listed in `AmbiguityPolicy`, which by default is an unstructured 423, 425
|
|
124
|
+
or 429 (`refusal_http_statuses`) or a `QUERY_LIMIT_EXCEEDED` / `OPERATION_TIME_LIMIT` refusal
|
|
125
|
+
(`refusal_api_codes`). A code or status added only to `RetryPolicy` is retried for `SAFE` work alone.
|
|
126
|
+
A physical batch applies these rules to each command: after a failure, only the commands that
|
|
127
|
+
may run again are sent again, in a smaller batch, and an `UNSAFE` command of a batch that may have run
|
|
128
|
+
arrives as unknown. Replay rounds spend the same attempt and retry-time budget as the sends before
|
|
129
|
+
them. Method names never imply safety;
|
|
78
130
|
mark a request `SAFE` only when the operation's semantics justify it.
|
|
79
131
|
|
|
80
132
|
Use `ExecutionPolicy` to narrow attempts or resource budgets for one operation:
|
|
@@ -87,6 +139,22 @@ one_attempt = ExecutionPolicy(max_attempts_per_request=1)
|
|
|
87
139
|
result = await client.call(request, policy=one_attempt)
|
|
88
140
|
```
|
|
89
141
|
|
|
142
|
+
A `policy=` argument replaces the client's default policy wholesale; fields are never merged. The
|
|
143
|
+
client default is `ExecutionPolicy.from_settings(settings)`: the library defaults with
|
|
144
|
+
`max_retry_elapsed_per_request` taken from `Settings.http_timeout` (30 s by default, while a bare
|
|
145
|
+
`ExecutionPolicy()` allows 120 s). To change one field and keep the configured timeout, derive the
|
|
146
|
+
per-call policy from that default:
|
|
147
|
+
|
|
148
|
+
<!-- tested: tests/settings_test.py::test_a_per_call_policy_replaces_the_client_default_without_merging -->
|
|
149
|
+
```python
|
|
150
|
+
import dataclasses
|
|
151
|
+
|
|
152
|
+
from b24api import ExecutionPolicy
|
|
153
|
+
|
|
154
|
+
one_attempt = dataclasses.replace(ExecutionPolicy.from_settings(settings), max_attempts_per_request=1)
|
|
155
|
+
result = await client.call(request, policy=one_attempt)
|
|
156
|
+
```
|
|
157
|
+
|
|
90
158
|
## Logical batch and correlation
|
|
91
159
|
|
|
92
160
|
`batch()` accepts an arbitrary-length synchronous or asynchronous command source. It consumes the
|
|
@@ -100,7 +168,7 @@ matching a result to the object, file, chat or database row that produced its re
|
|
|
100
168
|
<!-- tested: tests/client_v2_test.py::test_logical_batch_is_unbounded_ordered_and_correlation_is_strictly_off_wire -->
|
|
101
169
|
```python
|
|
102
170
|
from b24api import RouteKind
|
|
103
|
-
from b24api import Command, CommandSuccess
|
|
171
|
+
from b24api.contracts import Command, CommandSuccess
|
|
104
172
|
|
|
105
173
|
commands = (
|
|
106
174
|
Command(
|
|
@@ -121,7 +189,7 @@ async with client.batch(commands, batch_size=25) as stream:
|
|
|
121
189
|
|
|
122
190
|
<!-- tested: tests/client_v2_test.py::test_batch_outcomes_retains_typed_failure_without_halting_later_commands -->
|
|
123
191
|
```python
|
|
124
|
-
from b24api import CommandFailure, CommandNotExecuted, CommandOutcomeUnknown
|
|
192
|
+
from b24api.contracts import CommandFailure, CommandNotExecuted, CommandOutcomeUnknown
|
|
125
193
|
|
|
126
194
|
async with client.batch_outcomes(commands) as stream:
|
|
127
195
|
async for outcome in stream:
|
|
@@ -154,7 +222,16 @@ exact `limit_path`; the client never guesses method-specific parameter names.
|
|
|
154
222
|
|
|
155
223
|
### List traversal comparison
|
|
156
224
|
|
|
157
|
-

|
|
225
|
+

|
|
226
|
+
|
|
227
|
+
The animation replays traces executed against a scripted portal with 1,000 dense IDs at 50 rows
|
|
228
|
+
per page. `iter_list` sends 20 pages and one empty confirmation (21 HTTP). `iter_list_counted` sends
|
|
229
|
+
the head and one batch of 19 pages (2 HTTP). `iter_list_keyset` in auto mode selects range
|
|
230
|
+
execution: it reads both ends of the range in one batch, the 19 ranges between them in a second, and
|
|
231
|
+
confirms the end with one call for `ID > 1000` (3 HTTP). A `BoundedIdentityRange` with a qualified
|
|
232
|
+
upper ID needs no confirmation: sequential execution stops when it receives that ID. On a real portal
|
|
233
|
+
auto chooses from the observed geometry, so the plan and its request count can differ; the report's
|
|
234
|
+
`keyset_selection` says which plan ran.
|
|
158
235
|
|
|
159
236
|
### Sequential offset
|
|
160
237
|
|
|
@@ -199,7 +276,7 @@ sequence and records that degradation in the operation report.
|
|
|
199
276
|
<!-- tested: tests/client_findings_3_test.py::test_shape_rejection_is_retained_as_zero_admission_page_evidence -->
|
|
200
277
|
```python
|
|
201
278
|
from b24api import RouteKind
|
|
202
|
-
from b24api import ResultCollectionShape
|
|
279
|
+
from b24api.contracts import ResultCollectionShape
|
|
203
280
|
|
|
204
281
|
stream = client.iter_list(
|
|
205
282
|
Request("example.dictionary.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
@@ -232,6 +309,16 @@ stream = client.iter_list_counted(
|
|
|
232
309
|
Use it only when `total` is exact for the supplied filter and offset pages are stable. Any missing
|
|
233
310
|
range, overlap, duplicate identity or total contradiction raises `IncompleteTraversalError`.
|
|
234
311
|
|
|
312
|
+
A filtered call that matches nothing may omit `total` entirely, as `user.get` does with
|
|
313
|
+
`result: []`. A first page with no rows, no `next` and no usable `total` (missing, `null`, or the
|
|
314
|
+
`-1` unknown sentinel) therefore completes as an observed empty source after that one request: the
|
|
315
|
+
report is `completed` and `exhausted`, but its assurance is `mechanics_only` (`identity_exact` with an
|
|
316
|
+
identity), never a count-matched claim, and no total is invented. A first page that reports
|
|
317
|
+
`total: 0` keeps the count-matched result. Rows without a usable total, a remaining `next`, a
|
|
318
|
+
positive total with no rows, a fixed step, or a `ConsistencyPolicy` whose `confirmation_policy` is
|
|
319
|
+
`QUALIFIED_TOTAL` (from `b24api.contracts.policy.ConfirmationPolicy`) stay strict and raise
|
|
320
|
+
`IncompleteTraversalError`.
|
|
321
|
+
|
|
235
322
|
Physical batching reduces HTTP exchanges, but does not suppress server-side COUNT in ordinary
|
|
236
323
|
counted list subrequests. Each command still performs its own offset page retrieval and associated
|
|
237
324
|
total calculation on the server. Do not confuse batching these commands with a no-count traversal.
|
|
@@ -255,8 +342,10 @@ Static incompatibility with the auto contract raises `CapabilityError` from the
|
|
|
255
342
|
`iter_list_keyset(...)` call before iteration begins. A portal that accepts but contradicts the
|
|
256
343
|
declared controls fails before emission with `IncompleteTraversalError`; auto never restarts that
|
|
257
344
|
operation silently. `KeysetTraversal` inside reference traversal remains sequential-only. The
|
|
258
|
-
terminal report
|
|
259
|
-
|
|
345
|
+
terminal report carries a compact `keyset_selection` (`requested_kind`, `selected_kind`, `reason`)
|
|
346
|
+
for every keyset traversal. A `page_stop` callback needs the ordered page stream, so auto reports
|
|
347
|
+
`AUTO`, `SEQUENTIAL`, `PAGE_STOP`; an explicit `SequentialKeysetExecution()` reports
|
|
348
|
+
`EXPLICIT_SEQUENTIAL`. The detailed `keyset_execution` report is present only when the fast path ran.
|
|
260
349
|
|
|
261
350
|
Use `await client.verify_keyset_capability(...)` as a development/CI/staging guard on a stable
|
|
262
351
|
representative fixture. It performs five strict-bound checks and returns only a `VERIFIED` report;
|
|
@@ -337,10 +426,10 @@ boundary, use an application-owned direct-call workflow or supply a unique tie-b
|
|
|
337
426
|
For multiple parent-bound cursor chains, `iter_cursors()` keeps cursor progress and correlation
|
|
338
427
|
isolated per binding while ready pages share the physical batch queue.
|
|
339
428
|
|
|
340
|
-

|
|
429
|
+

|
|
341
430
|
|
|
342
|
-
See [architecture](docs/architecture.md), [migration](docs/migration.md),
|
|
343
|
-
[performance](docs/performance.md), and [endpoint recipes](docs/recipes.md) for the complete
|
|
431
|
+
See [architecture](https://github.com/shkarupa-alex/b24api/blob/master/docs/architecture.md), [migration](https://github.com/shkarupa-alex/b24api/blob/master/docs/migration.md),
|
|
432
|
+
[performance](https://github.com/shkarupa-alex/b24api/blob/master/docs/performance.md), and [endpoint recipes](https://github.com/shkarupa-alex/b24api/blob/master/docs/recipes.md) for the complete
|
|
344
433
|
contracts and selection guidance.
|
|
345
434
|
|
|
346
435
|
### One list method across many parent entities
|
|
@@ -349,20 +438,13 @@ contracts and selection guidance.
|
|
|
349
438
|
client remains unaware of entity types: a binding can represent a deal, lead, chat or any other
|
|
350
439
|
caller-defined parent.
|
|
351
440
|
|
|
352
|
-

|
|
441
|
+

|
|
353
442
|
|
|
354
443
|
<!-- tested: tests/client_v2_test.py::test_bound_references_apply_nested_updates_off_wire_and_emit_exact_completion -->
|
|
355
444
|
```python
|
|
356
445
|
from b24api import RouteKind
|
|
357
|
-
from b24api import
|
|
358
|
-
|
|
359
|
-
Binding,
|
|
360
|
-
ParameterPath,
|
|
361
|
-
ParameterUpdate,
|
|
362
|
-
ReferenceComplete,
|
|
363
|
-
ReferenceItem,
|
|
364
|
-
SequentialTraversal,
|
|
365
|
-
)
|
|
446
|
+
from b24api import BatchDispatch, Binding, ParameterPath, ParameterUpdate, SequentialTraversal
|
|
447
|
+
from b24api.contracts import ReferenceComplete, ReferenceItem
|
|
366
448
|
|
|
367
449
|
bindings = (
|
|
368
450
|
Binding(
|
|
@@ -518,21 +600,22 @@ uv run --with memray memray stats /tmp/b24api.bin
|
|
|
518
600
|
```
|
|
519
601
|
|
|
520
602
|
These deterministic fixtures characterize local resources and network shape; they are not live
|
|
521
|
-
portal latency admission. See [docs/performance.md](docs/performance.md) for current measurements
|
|
522
|
-
and [docs/architecture.md](docs/architecture.md) for guarantees and ownership boundaries.
|
|
603
|
+
portal latency admission. See [docs/performance.md](https://github.com/shkarupa-alex/b24api/blob/master/docs/performance.md) for current measurements
|
|
604
|
+
and [docs/architecture.md](https://github.com/shkarupa-alex/b24api/blob/master/docs/architecture.md) for guarantees and ownership boundaries.
|
|
523
605
|
|
|
524
|
-
Projects moving from an earlier API surface can use [docs/migration.md](docs/migration.md).
|
|
606
|
+
Projects moving from an earlier API surface can use [docs/migration.md](https://github.com/shkarupa-alex/b24api/blob/master/docs/migration.md).
|
|
525
607
|
|
|
526
608
|
## Verification
|
|
527
609
|
|
|
528
610
|
```console
|
|
529
611
|
uv sync --frozen
|
|
530
|
-
|
|
531
|
-
.venv/bin/ruff check . --no-fix --no-cache
|
|
532
|
-
.venv/bin/ruff format --check . --no-cache
|
|
533
|
-
.venv/bin/mypy --strict b24api tools/b24api_evidence
|
|
612
|
+
make qc
|
|
534
613
|
git diff --check
|
|
535
614
|
```
|
|
536
615
|
|
|
616
|
+
`make qc` runs the lint, type and default test checks that CI blocks on. It leaves out the internal
|
|
617
|
+
benches (the pytest marker `slow`: the evidence harness contracts and the 50k/100k-scale runs, which
|
|
618
|
+
take several minutes). `make bench` runs them, and so does the blocking CI job `slow`.
|
|
619
|
+
|
|
537
620
|
The wheel regression installs into an isolated environment, executes the `b24api` entry point and
|
|
538
621
|
checks that tests, live/evidence tooling and credentials are excluded.
|
|
@@ -1,16 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
Name: b24api
|
|
3
|
-
Version: 2.3.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.29,>=0.28.1
|
|
9
|
-
Requires-Dist: pydantic>=2.11.7
|
|
10
|
-
Requires-Dist: pydantic-settings>=2.10.1
|
|
11
|
-
Dynamic: license-file
|
|
12
|
-
|
|
13
|
-
# b24api 2.x
|
|
1
|
+
# b24api 3.x
|
|
14
2
|
|
|
15
3
|
`b24api` is a thin asynchronous Bitrix24 REST client for Python 3.12+. It knows how to send
|
|
16
4
|
requests, split logical batches, traverse lists, retry safely, preserve caller correlation and
|
|
@@ -27,16 +15,42 @@ export BITRIX24_API_WEBHOOK_URL='https://portal.example/rest/.../'
|
|
|
27
15
|
Keep the webhook out of source, logs and command arguments. Reuse one client for a related unit of
|
|
28
16
|
work so its HTTP/2 connection pool and rate state are reused.
|
|
29
17
|
|
|
30
|
-
|
|
18
|
+
## Quickstart
|
|
19
|
+
|
|
20
|
+
<!-- tested: tests/readme_test.py::test_quickstart_runs_exactly_against_a_scripted_portal -->
|
|
31
21
|
```python
|
|
32
|
-
|
|
22
|
+
import os
|
|
23
|
+
|
|
24
|
+
from b24api import Bitrix24, Request
|
|
33
25
|
|
|
34
|
-
async with Bitrix24() as client:
|
|
35
|
-
|
|
26
|
+
async with Bitrix24.from_webhook(os.environ["BITRIX24_API_WEBHOOK_URL"]) as client:
|
|
27
|
+
deals = client.iter_list(Request.bare("crm.deal.list", {"select": ["ID", "TITLE"]}))
|
|
28
|
+
async for deal in deals:
|
|
29
|
+
print(deal["ID"], deal["TITLE"])
|
|
30
|
+
print(deals.report.state)
|
|
36
31
|
```
|
|
37
32
|
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
Four things are at work:
|
|
34
|
+
|
|
35
|
+
- **The client.** `Bitrix24.from_webhook()` checks the URL and owns the connection pool it opens;
|
|
36
|
+
`async with` closes it. `Bitrix24()` reads the same URL from the environment.
|
|
37
|
+
- **The request.** `Request.bare()` names a REST method and its parameters and sends them to the
|
|
38
|
+
classic `/rest/` endpoint. The route is always explicit: `Request.v3()` targets the V3 API.
|
|
39
|
+
- **`iter_list()`.** It walks the list page by page and reads one more, empty, page to confirm
|
|
40
|
+
the end.
|
|
41
|
+
- **The report.** `deals.report` says how the traversal ended. `completed` means every page was
|
|
42
|
+
read; a traversal that stops early or fails records why.
|
|
43
|
+
|
|
44
|
+
A single call returns the decoded `result`:
|
|
45
|
+
|
|
46
|
+
<!-- tested: tests/readme_test.py::test_quickstart_runs_exactly_against_a_scripted_portal -->
|
|
47
|
+
```python
|
|
48
|
+
users = await client.call(Request.bare("user.get", {"ID": 1}))
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The client owns the transport it creates. An injected transport remains caller-owned. `aclose()` is
|
|
52
|
+
idempotent and closes active streams before the owned transport. To prove that no row is missing or
|
|
53
|
+
repeated, give the traversal an identity (see [Choosing a list operation](#choosing-a-list-operation)).
|
|
40
54
|
|
|
41
55
|
## Direct calls
|
|
42
56
|
|
|
@@ -71,10 +85,17 @@ may already have reached Bitrix:
|
|
|
71
85
|
| Value | Meaning | After possible dispatch |
|
|
72
86
|
|---|---|---|
|
|
73
87
|
| `SAFE` | Repeating the request cannot create a second business effect. Typical reads and explicitly idempotent operations belong here. | Automatic retry is allowed within policy budgets. |
|
|
74
|
-
| `UNSAFE` | Repeating the request is known to risk a duplicate effect, for example creating an entity without an idempotency key. | No automatic replay; the caller receives an ambiguous-execution error and reconciles state. |
|
|
88
|
+
| `UNSAFE` | Repeating the request is known to risk a duplicate effect, for example creating an entity without an idempotency key. | No automatic replay; the caller receives an ambiguous-execution error and reconciles state. It is retried only when the failure proves it did not run. |
|
|
75
89
|
| `UNKNOWN` | The caller has not established whether replay is safe. This is the default. | Same conservative behavior as `UNSAFE`, while diagnostics preserve that safety was unknown rather than known unsafe. |
|
|
76
90
|
|
|
77
|
-
A failure
|
|
91
|
+
A failure that proves the request did not run is retried whatever its safety: a transport failure
|
|
92
|
+
before dispatch, or a refusal listed in `AmbiguityPolicy`, which by default is an unstructured 423, 425
|
|
93
|
+
or 429 (`refusal_http_statuses`) or a `QUERY_LIMIT_EXCEEDED` / `OPERATION_TIME_LIMIT` refusal
|
|
94
|
+
(`refusal_api_codes`). A code or status added only to `RetryPolicy` is retried for `SAFE` work alone.
|
|
95
|
+
A physical batch applies these rules to each command: after a failure, only the commands that
|
|
96
|
+
may run again are sent again, in a smaller batch, and an `UNSAFE` command of a batch that may have run
|
|
97
|
+
arrives as unknown. Replay rounds spend the same attempt and retry-time budget as the sends before
|
|
98
|
+
them. Method names never imply safety;
|
|
78
99
|
mark a request `SAFE` only when the operation's semantics justify it.
|
|
79
100
|
|
|
80
101
|
Use `ExecutionPolicy` to narrow attempts or resource budgets for one operation:
|
|
@@ -87,6 +108,22 @@ one_attempt = ExecutionPolicy(max_attempts_per_request=1)
|
|
|
87
108
|
result = await client.call(request, policy=one_attempt)
|
|
88
109
|
```
|
|
89
110
|
|
|
111
|
+
A `policy=` argument replaces the client's default policy wholesale; fields are never merged. The
|
|
112
|
+
client default is `ExecutionPolicy.from_settings(settings)`: the library defaults with
|
|
113
|
+
`max_retry_elapsed_per_request` taken from `Settings.http_timeout` (30 s by default, while a bare
|
|
114
|
+
`ExecutionPolicy()` allows 120 s). To change one field and keep the configured timeout, derive the
|
|
115
|
+
per-call policy from that default:
|
|
116
|
+
|
|
117
|
+
<!-- tested: tests/settings_test.py::test_a_per_call_policy_replaces_the_client_default_without_merging -->
|
|
118
|
+
```python
|
|
119
|
+
import dataclasses
|
|
120
|
+
|
|
121
|
+
from b24api import ExecutionPolicy
|
|
122
|
+
|
|
123
|
+
one_attempt = dataclasses.replace(ExecutionPolicy.from_settings(settings), max_attempts_per_request=1)
|
|
124
|
+
result = await client.call(request, policy=one_attempt)
|
|
125
|
+
```
|
|
126
|
+
|
|
90
127
|
## Logical batch and correlation
|
|
91
128
|
|
|
92
129
|
`batch()` accepts an arbitrary-length synchronous or asynchronous command source. It consumes the
|
|
@@ -100,7 +137,7 @@ matching a result to the object, file, chat or database row that produced its re
|
|
|
100
137
|
<!-- tested: tests/client_v2_test.py::test_logical_batch_is_unbounded_ordered_and_correlation_is_strictly_off_wire -->
|
|
101
138
|
```python
|
|
102
139
|
from b24api import RouteKind
|
|
103
|
-
from b24api import Command, CommandSuccess
|
|
140
|
+
from b24api.contracts import Command, CommandSuccess
|
|
104
141
|
|
|
105
142
|
commands = (
|
|
106
143
|
Command(
|
|
@@ -121,7 +158,7 @@ async with client.batch(commands, batch_size=25) as stream:
|
|
|
121
158
|
|
|
122
159
|
<!-- tested: tests/client_v2_test.py::test_batch_outcomes_retains_typed_failure_without_halting_later_commands -->
|
|
123
160
|
```python
|
|
124
|
-
from b24api import CommandFailure, CommandNotExecuted, CommandOutcomeUnknown
|
|
161
|
+
from b24api.contracts import CommandFailure, CommandNotExecuted, CommandOutcomeUnknown
|
|
125
162
|
|
|
126
163
|
async with client.batch_outcomes(commands) as stream:
|
|
127
164
|
async for outcome in stream:
|
|
@@ -154,7 +191,16 @@ exact `limit_path`; the client never guesses method-specific parameter names.
|
|
|
154
191
|
|
|
155
192
|
### List traversal comparison
|
|
156
193
|
|
|
157
|
-

|
|
194
|
+

|
|
195
|
+
|
|
196
|
+
The animation replays traces executed against a scripted portal with 1,000 dense IDs at 50 rows
|
|
197
|
+
per page. `iter_list` sends 20 pages and one empty confirmation (21 HTTP). `iter_list_counted` sends
|
|
198
|
+
the head and one batch of 19 pages (2 HTTP). `iter_list_keyset` in auto mode selects range
|
|
199
|
+
execution: it reads both ends of the range in one batch, the 19 ranges between them in a second, and
|
|
200
|
+
confirms the end with one call for `ID > 1000` (3 HTTP). A `BoundedIdentityRange` with a qualified
|
|
201
|
+
upper ID needs no confirmation: sequential execution stops when it receives that ID. On a real portal
|
|
202
|
+
auto chooses from the observed geometry, so the plan and its request count can differ; the report's
|
|
203
|
+
`keyset_selection` says which plan ran.
|
|
158
204
|
|
|
159
205
|
### Sequential offset
|
|
160
206
|
|
|
@@ -199,7 +245,7 @@ sequence and records that degradation in the operation report.
|
|
|
199
245
|
<!-- tested: tests/client_findings_3_test.py::test_shape_rejection_is_retained_as_zero_admission_page_evidence -->
|
|
200
246
|
```python
|
|
201
247
|
from b24api import RouteKind
|
|
202
|
-
from b24api import ResultCollectionShape
|
|
248
|
+
from b24api.contracts import ResultCollectionShape
|
|
203
249
|
|
|
204
250
|
stream = client.iter_list(
|
|
205
251
|
Request("example.dictionary.list", replay_safety=ReplaySafety.SAFE, route=RouteKind.BARE),
|
|
@@ -232,6 +278,16 @@ stream = client.iter_list_counted(
|
|
|
232
278
|
Use it only when `total` is exact for the supplied filter and offset pages are stable. Any missing
|
|
233
279
|
range, overlap, duplicate identity or total contradiction raises `IncompleteTraversalError`.
|
|
234
280
|
|
|
281
|
+
A filtered call that matches nothing may omit `total` entirely, as `user.get` does with
|
|
282
|
+
`result: []`. A first page with no rows, no `next` and no usable `total` (missing, `null`, or the
|
|
283
|
+
`-1` unknown sentinel) therefore completes as an observed empty source after that one request: the
|
|
284
|
+
report is `completed` and `exhausted`, but its assurance is `mechanics_only` (`identity_exact` with an
|
|
285
|
+
identity), never a count-matched claim, and no total is invented. A first page that reports
|
|
286
|
+
`total: 0` keeps the count-matched result. Rows without a usable total, a remaining `next`, a
|
|
287
|
+
positive total with no rows, a fixed step, or a `ConsistencyPolicy` whose `confirmation_policy` is
|
|
288
|
+
`QUALIFIED_TOTAL` (from `b24api.contracts.policy.ConfirmationPolicy`) stay strict and raise
|
|
289
|
+
`IncompleteTraversalError`.
|
|
290
|
+
|
|
235
291
|
Physical batching reduces HTTP exchanges, but does not suppress server-side COUNT in ordinary
|
|
236
292
|
counted list subrequests. Each command still performs its own offset page retrieval and associated
|
|
237
293
|
total calculation on the server. Do not confuse batching these commands with a no-count traversal.
|
|
@@ -255,8 +311,10 @@ Static incompatibility with the auto contract raises `CapabilityError` from the
|
|
|
255
311
|
`iter_list_keyset(...)` call before iteration begins. A portal that accepts but contradicts the
|
|
256
312
|
declared controls fails before emission with `IncompleteTraversalError`; auto never restarts that
|
|
257
313
|
operation silently. `KeysetTraversal` inside reference traversal remains sequential-only. The
|
|
258
|
-
terminal report
|
|
259
|
-
|
|
314
|
+
terminal report carries a compact `keyset_selection` (`requested_kind`, `selected_kind`, `reason`)
|
|
315
|
+
for every keyset traversal. A `page_stop` callback needs the ordered page stream, so auto reports
|
|
316
|
+
`AUTO`, `SEQUENTIAL`, `PAGE_STOP`; an explicit `SequentialKeysetExecution()` reports
|
|
317
|
+
`EXPLICIT_SEQUENTIAL`. The detailed `keyset_execution` report is present only when the fast path ran.
|
|
260
318
|
|
|
261
319
|
Use `await client.verify_keyset_capability(...)` as a development/CI/staging guard on a stable
|
|
262
320
|
representative fixture. It performs five strict-bound checks and returns only a `VERIFIED` report;
|
|
@@ -337,10 +395,10 @@ boundary, use an application-owned direct-call workflow or supply a unique tie-b
|
|
|
337
395
|
For multiple parent-bound cursor chains, `iter_cursors()` keeps cursor progress and correlation
|
|
338
396
|
isolated per binding while ready pages share the physical batch queue.
|
|
339
397
|
|
|
340
|
-

|
|
398
|
+

|
|
341
399
|
|
|
342
|
-
See [architecture](docs/architecture.md), [migration](docs/migration.md),
|
|
343
|
-
[performance](docs/performance.md), and [endpoint recipes](docs/recipes.md) for the complete
|
|
400
|
+
See [architecture](https://github.com/shkarupa-alex/b24api/blob/master/docs/architecture.md), [migration](https://github.com/shkarupa-alex/b24api/blob/master/docs/migration.md),
|
|
401
|
+
[performance](https://github.com/shkarupa-alex/b24api/blob/master/docs/performance.md), and [endpoint recipes](https://github.com/shkarupa-alex/b24api/blob/master/docs/recipes.md) for the complete
|
|
344
402
|
contracts and selection guidance.
|
|
345
403
|
|
|
346
404
|
### One list method across many parent entities
|
|
@@ -349,20 +407,13 @@ contracts and selection guidance.
|
|
|
349
407
|
client remains unaware of entity types: a binding can represent a deal, lead, chat or any other
|
|
350
408
|
caller-defined parent.
|
|
351
409
|
|
|
352
|
-

|
|
410
|
+

|
|
353
411
|
|
|
354
412
|
<!-- tested: tests/client_v2_test.py::test_bound_references_apply_nested_updates_off_wire_and_emit_exact_completion -->
|
|
355
413
|
```python
|
|
356
414
|
from b24api import RouteKind
|
|
357
|
-
from b24api import
|
|
358
|
-
|
|
359
|
-
Binding,
|
|
360
|
-
ParameterPath,
|
|
361
|
-
ParameterUpdate,
|
|
362
|
-
ReferenceComplete,
|
|
363
|
-
ReferenceItem,
|
|
364
|
-
SequentialTraversal,
|
|
365
|
-
)
|
|
415
|
+
from b24api import BatchDispatch, Binding, ParameterPath, ParameterUpdate, SequentialTraversal
|
|
416
|
+
from b24api.contracts import ReferenceComplete, ReferenceItem
|
|
366
417
|
|
|
367
418
|
bindings = (
|
|
368
419
|
Binding(
|
|
@@ -518,21 +569,22 @@ uv run --with memray memray stats /tmp/b24api.bin
|
|
|
518
569
|
```
|
|
519
570
|
|
|
520
571
|
These deterministic fixtures characterize local resources and network shape; they are not live
|
|
521
|
-
portal latency admission. See [docs/performance.md](docs/performance.md) for current measurements
|
|
522
|
-
and [docs/architecture.md](docs/architecture.md) for guarantees and ownership boundaries.
|
|
572
|
+
portal latency admission. See [docs/performance.md](https://github.com/shkarupa-alex/b24api/blob/master/docs/performance.md) for current measurements
|
|
573
|
+
and [docs/architecture.md](https://github.com/shkarupa-alex/b24api/blob/master/docs/architecture.md) for guarantees and ownership boundaries.
|
|
523
574
|
|
|
524
|
-
Projects moving from an earlier API surface can use [docs/migration.md](docs/migration.md).
|
|
575
|
+
Projects moving from an earlier API surface can use [docs/migration.md](https://github.com/shkarupa-alex/b24api/blob/master/docs/migration.md).
|
|
525
576
|
|
|
526
577
|
## Verification
|
|
527
578
|
|
|
528
579
|
```console
|
|
529
580
|
uv sync --frozen
|
|
530
|
-
|
|
531
|
-
.venv/bin/ruff check . --no-fix --no-cache
|
|
532
|
-
.venv/bin/ruff format --check . --no-cache
|
|
533
|
-
.venv/bin/mypy --strict b24api tools/b24api_evidence
|
|
581
|
+
make qc
|
|
534
582
|
git diff --check
|
|
535
583
|
```
|
|
536
584
|
|
|
585
|
+
`make qc` runs the lint, type and default test checks that CI blocks on. It leaves out the internal
|
|
586
|
+
benches (the pytest marker `slow`: the evidence harness contracts and the 50k/100k-scale runs, which
|
|
587
|
+
take several minutes). `make bench` runs them, and so does the blocking CI job `slow`.
|
|
588
|
+
|
|
537
589
|
The wheel regression installs into an isolated environment, executes the `b24api` entry point and
|
|
538
590
|
checks that tests, live/evidence tooling and credentials are excluded.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""One explicit, method-agnostic b24api public surface.
|
|
2
|
+
|
|
3
|
+
The root exports the names a typical application needs. Every other public name lives in one of the
|
|
4
|
+
public aggregators listed in ``b24api.migration.PUBLIC_NAMESPACES``. A name that left the root in 3.0
|
|
5
|
+
does not resolve here; ``b24api.Name`` raises an ``AttributeError`` naming its new import path.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import importlib
|
|
9
|
+
from typing import TYPE_CHECKING
|
|
10
|
+
|
|
11
|
+
from b24api.client import (
|
|
12
|
+
Bitrix24,
|
|
13
|
+
)
|
|
14
|
+
from b24api.contracts import (
|
|
15
|
+
AmbiguityPolicy,
|
|
16
|
+
AutoKeysetExecution,
|
|
17
|
+
BatchDispatch,
|
|
18
|
+
Binding,
|
|
19
|
+
BoundedIdentityRange,
|
|
20
|
+
CountedTraversal,
|
|
21
|
+
CursorSpec,
|
|
22
|
+
CursorTraversal,
|
|
23
|
+
DirectDispatch,
|
|
24
|
+
ExecutionPolicy,
|
|
25
|
+
IdentityCoercion,
|
|
26
|
+
IdentitySpec,
|
|
27
|
+
KeysetSpec,
|
|
28
|
+
KeysetTraversal,
|
|
29
|
+
OffsetContinuation,
|
|
30
|
+
OffsetSpec,
|
|
31
|
+
OperationReport,
|
|
32
|
+
PageAdapter,
|
|
33
|
+
PageStopPolicy,
|
|
34
|
+
ParameterPath,
|
|
35
|
+
ParameterUpdate,
|
|
36
|
+
PartitionedKeysetExecution,
|
|
37
|
+
RangeKeysetExecution,
|
|
38
|
+
ReplaySafety,
|
|
39
|
+
Request,
|
|
40
|
+
ResultSelector,
|
|
41
|
+
RetryPolicy,
|
|
42
|
+
RouteKind,
|
|
43
|
+
SequentialKeysetExecution,
|
|
44
|
+
SequentialTraversal,
|
|
45
|
+
StableIntegerKeysetContract,
|
|
46
|
+
TerminalState,
|
|
47
|
+
TotalTermination,
|
|
48
|
+
TraversalAssurance,
|
|
49
|
+
)
|
|
50
|
+
from b24api.errors import (
|
|
51
|
+
AmbiguousExecutionError,
|
|
52
|
+
ApiResponseError,
|
|
53
|
+
B24ApiError,
|
|
54
|
+
BatchFailed,
|
|
55
|
+
BudgetExceededError,
|
|
56
|
+
CapabilityError,
|
|
57
|
+
IncompleteTraversalError,
|
|
58
|
+
KeysetCapabilityError,
|
|
59
|
+
PaginationError,
|
|
60
|
+
ProtocolError,
|
|
61
|
+
ReferenceFailed,
|
|
62
|
+
TransportError,
|
|
63
|
+
)
|
|
64
|
+
from b24api.settings import (
|
|
65
|
+
Settings,
|
|
66
|
+
)
|
|
67
|
+
from b24api.transport import (
|
|
68
|
+
HttpxTransport,
|
|
69
|
+
Transport,
|
|
70
|
+
WireTransport,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
__all__ = [
|
|
74
|
+
"AmbiguityPolicy",
|
|
75
|
+
"AmbiguousExecutionError",
|
|
76
|
+
"ApiResponseError",
|
|
77
|
+
"AutoKeysetExecution",
|
|
78
|
+
"B24ApiError",
|
|
79
|
+
"BatchDispatch",
|
|
80
|
+
"BatchFailed",
|
|
81
|
+
"Binding",
|
|
82
|
+
"Bitrix24",
|
|
83
|
+
"BoundedIdentityRange",
|
|
84
|
+
"BudgetExceededError",
|
|
85
|
+
"CapabilityError",
|
|
86
|
+
"CountedTraversal",
|
|
87
|
+
"CursorSpec",
|
|
88
|
+
"CursorTraversal",
|
|
89
|
+
"DirectDispatch",
|
|
90
|
+
"ExecutionPolicy",
|
|
91
|
+
"HttpxTransport",
|
|
92
|
+
"IdentityCoercion",
|
|
93
|
+
"IdentitySpec",
|
|
94
|
+
"IncompleteTraversalError",
|
|
95
|
+
"KeysetCapabilityError",
|
|
96
|
+
"KeysetSpec",
|
|
97
|
+
"KeysetTraversal",
|
|
98
|
+
"OffsetContinuation",
|
|
99
|
+
"OffsetSpec",
|
|
100
|
+
"OperationReport",
|
|
101
|
+
"PageAdapter",
|
|
102
|
+
"PageStopPolicy",
|
|
103
|
+
"PaginationError",
|
|
104
|
+
"ParameterPath",
|
|
105
|
+
"ParameterUpdate",
|
|
106
|
+
"PartitionedKeysetExecution",
|
|
107
|
+
"ProtocolError",
|
|
108
|
+
"RangeKeysetExecution",
|
|
109
|
+
"ReferenceFailed",
|
|
110
|
+
"ReplaySafety",
|
|
111
|
+
"Request",
|
|
112
|
+
"ResultSelector",
|
|
113
|
+
"RetryPolicy",
|
|
114
|
+
"RouteKind",
|
|
115
|
+
"SequentialKeysetExecution",
|
|
116
|
+
"SequentialTraversal",
|
|
117
|
+
"Settings",
|
|
118
|
+
"StableIntegerKeysetContract",
|
|
119
|
+
"TerminalState",
|
|
120
|
+
"TotalTermination",
|
|
121
|
+
"Transport",
|
|
122
|
+
"TransportError",
|
|
123
|
+
"TraversalAssurance",
|
|
124
|
+
"WireTransport",
|
|
125
|
+
]
|
|
126
|
+
|
|
127
|
+
if not TYPE_CHECKING:
|
|
128
|
+
# Hidden from type checkers: a visible module ``__getattr__`` would let every moved root import type-check.
|
|
129
|
+
def __getattr__(name: str) -> object:
|
|
130
|
+
"""Fail for any unknown name, naming the new import path of a name that moved out of the root."""
|
|
131
|
+
# Loaded on first use, so ``python -m b24api.migration`` does not find it already imported.
|
|
132
|
+
package = importlib.import_module("b24api.migration").ROOT_MOVES.get(name)
|
|
133
|
+
message = f"module 'b24api' has no attribute {name!r}"
|
|
134
|
+
raise AttributeError(message if package is None else f"{message}; it moved to {package}.{name} in 3.0")
|