b24api 2.0.0__tar.gz → 2.1.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.0.0/b24api.egg-info → b24api-2.1.0}/PKG-INFO +48 -6
- {b24api-2.0.0 → b24api-2.1.0}/README.md +47 -5
- {b24api-2.0.0 → b24api-2.1.0}/b24api/__init__.py +88 -1
- b24api-2.1.0/b24api/_audit.py +78 -0
- b24api-2.1.0/b24api/_client_traversal.py +170 -0
- b24api-2.1.0/b24api/_error_types.py +31 -0
- {b24api-2.0.0 → b24api-2.1.0}/b24api/_stream.py +30 -7
- {b24api-2.0.0 → b24api-2.1.0}/b24api/batch/engine.py +200 -73
- {b24api-2.0.0 → b24api-2.1.0}/b24api/batch/facade.py +7 -1
- {b24api-2.0.0 → b24api-2.1.0}/b24api/batch/stream.py +1 -5
- {b24api-2.0.0 → b24api-2.1.0}/b24api/cli.py +27 -1
- {b24api-2.0.0 → b24api-2.1.0}/b24api/cli_contract.py +78 -9
- {b24api-2.0.0 → b24api-2.1.0}/b24api/client.py +49 -124
- {b24api-2.0.0 → b24api-2.1.0}/b24api/contracts/__init__.py +83 -2
- {b24api-2.0.0 → b24api-2.1.0}/b24api/contracts/json.py +66 -4
- b24api-2.1.0/b24api/contracts/keyset_execution.py +232 -0
- {b24api-2.0.0 → b24api-2.1.0}/b24api/contracts/policy.py +80 -2
- b24api-2.1.0/b24api/contracts/report.py +385 -0
- {b24api-2.0.0 → b24api-2.1.0}/b24api/contracts/request.py +147 -6
- {b24api-2.0.0 → b24api-2.1.0}/b24api/contracts/response.py +77 -0
- b24api-2.1.0/b24api/contracts/traversal.py +262 -0
- b24api-2.1.0/b24api/contracts/wire.py +85 -0
- b24api-2.1.0/b24api/encoding.py +40 -0
- {b24api-2.0.0 → b24api-2.1.0}/b24api/errors.py +149 -54
- {b24api-2.0.0 → b24api-2.1.0}/b24api/execution/context.py +15 -0
- b24api-2.1.0/b24api/execution/executor.py +700 -0
- b24api-2.1.0/b24api/execution/failure.py +131 -0
- {b24api-2.0.0 → b24api-2.1.0}/b24api/execution/snapshot.py +7 -1
- {b24api-2.0.0 → b24api-2.1.0}/b24api/references/binding.py +35 -25
- {b24api-2.0.0 → b24api-2.1.0}/b24api/references/dispatch.py +23 -7
- {b24api-2.0.0 → b24api-2.1.0}/b24api/references/facade.py +58 -13
- {b24api-2.0.0 → b24api-2.1.0}/b24api/references/fanout.py +6 -1
- {b24api-2.0.0 → b24api-2.1.0}/b24api/references/scheduler.py +100 -10
- {b24api-2.0.0 → b24api-2.1.0}/b24api/references/stream.py +20 -6
- {b24api-2.0.0 → b24api-2.1.0}/b24api/references/support.py +0 -6
- b24api-2.1.0/b24api/testing/__init__.py +17 -0
- b24api-2.1.0/b24api/testing/_isolation.py +81 -0
- b24api-2.1.0/b24api/testing/transport.py +373 -0
- b24api-2.1.0/b24api/transport/__init__.py +6 -0
- b24api-2.1.0/b24api/transport/base.py +158 -0
- {b24api-2.0.0 → b24api-2.1.0}/b24api/transport/httpx.py +54 -12
- {b24api-2.0.0 → b24api-2.1.0}/b24api/transport/protocol.py +1 -1
- {b24api-2.0.0 → b24api-2.1.0}/b24api/traversal/counted.py +11 -8
- {b24api-2.0.0 → b24api-2.1.0}/b24api/traversal/counted_batch.py +111 -35
- {b24api-2.0.0 → b24api-2.1.0}/b24api/traversal/cursor.py +20 -14
- {b24api-2.0.0 → b24api-2.1.0}/b24api/traversal/driver.py +321 -38
- {b24api-2.0.0 → b24api-2.1.0}/b24api/traversal/facade.py +118 -32
- {b24api-2.0.0 → b24api-2.1.0}/b24api/traversal/identity.py +28 -19
- b24api-2.1.0/b24api/traversal/keyset.py +63 -0
- b24api-2.1.0/b24api/traversal/keyset_auto.py +241 -0
- b24api-2.1.0/b24api/traversal/keyset_capability.py +392 -0
- b24api-2.1.0/b24api/traversal/keyset_costs.py +185 -0
- b24api-2.1.0/b24api/traversal/keyset_eligibility.py +159 -0
- b24api-2.1.0/b24api/traversal/keyset_fast_plan.py +288 -0
- b24api-2.1.0/b24api/traversal/keyset_fast_stream.py +274 -0
- b24api-2.1.0/b24api/traversal/keyset_observation.py +270 -0
- b24api-2.1.0/b24api/traversal/keyset_partition.py +27 -0
- b24api-2.1.0/b24api/traversal/keyset_range.py +80 -0
- b24api-2.1.0/b24api/traversal/keyset_reporting.py +112 -0
- b24api-2.1.0/b24api/traversal/keyset_scheduler.py +672 -0
- b24api-2.1.0/b24api/traversal/keyset_step.py +138 -0
- b24api-2.1.0/b24api/traversal/keyset_transaction_contract.py +108 -0
- b24api-2.1.0/b24api/traversal/keyset_transactions.py +393 -0
- b24api-2.1.0/b24api/traversal/ordered_admission.py +182 -0
- b24api-2.1.0/b24api/traversal/page_validation.py +235 -0
- {b24api-2.0.0 → b24api-2.1.0}/b24api/traversal/plans.py +18 -10
- b24api-2.1.0/b24api/traversal/sequential.py +198 -0
- {b24api-2.0.0 → b24api-2.1.0}/b24api/traversal/stream.py +15 -6
- {b24api-2.0.0 → b24api-2.1.0}/b24api/traversal/values.py +56 -9
- {b24api-2.0.0 → b24api-2.1.0/b24api.egg-info}/PKG-INFO +48 -6
- {b24api-2.0.0 → b24api-2.1.0}/b24api.egg-info/SOURCES.txt +26 -0
- b24api-2.0.0/b24api/contracts/report.py +0 -137
- b24api-2.0.0/b24api/contracts/traversal.py +0 -140
- b24api-2.0.0/b24api/execution/executor.py +0 -339
- b24api-2.0.0/b24api/transport/__init__.py +0 -6
- b24api-2.0.0/b24api/transport/base.py +0 -56
- b24api-2.0.0/b24api/traversal/keyset.py +0 -71
- b24api-2.0.0/b24api/traversal/sequential.py +0 -149
- {b24api-2.0.0 → b24api-2.1.0}/LICENSE +0 -0
- {b24api-2.0.0 → b24api-2.1.0}/MANIFEST.in +0 -0
- {b24api-2.0.0 → b24api-2.1.0}/b24api/batch/__init__.py +0 -0
- {b24api-2.0.0 → b24api-2.1.0}/b24api/batch/logical.py +0 -0
- {b24api-2.0.0 → b24api-2.1.0}/b24api/batch/outcome.py +0 -0
- {b24api-2.0.0 → b24api-2.1.0}/b24api/contracts/command.py +0 -0
- {b24api-2.0.0 → b24api-2.1.0}/b24api/contracts/dispatch.py +0 -0
- {b24api-2.0.0 → b24api-2.1.0}/b24api/contracts/reference.py +0 -0
- {b24api-2.0.0 → b24api-2.1.0}/b24api/contracts/stream.py +0 -0
- {b24api-2.0.0 → b24api-2.1.0}/b24api/execution/__init__.py +0 -0
- {b24api-2.0.0 → b24api-2.1.0}/b24api/execution/cleanup.py +0 -0
- {b24api-2.0.0 → b24api-2.1.0}/b24api/execution/rate.py +0 -0
- {b24api-2.0.0 → b24api-2.1.0}/b24api/redaction.py +0 -0
- {b24api-2.0.0 → b24api-2.1.0}/b24api/references/__init__.py +0 -0
- {b24api-2.0.0 → b24api-2.1.0}/b24api/references/outcome.py +0 -0
- {b24api-2.0.0 → b24api-2.1.0}/b24api/settings.py +0 -0
- {b24api-2.0.0 → b24api-2.1.0}/b24api/traversal/__init__.py +0 -0
- {b24api-2.0.0 → b24api-2.1.0}/b24api.egg-info/dependency_links.txt +0 -0
- {b24api-2.0.0 → b24api-2.1.0}/b24api.egg-info/entry_points.txt +0 -0
- {b24api-2.0.0 → b24api-2.1.0}/b24api.egg-info/requires.txt +0 -0
- {b24api-2.0.0 → b24api-2.1.0}/b24api.egg-info/top_level.txt +0 -0
- {b24api-2.0.0 → b24api-2.1.0}/pyproject.toml +0 -0
- {b24api-2.0.0 → b24api-2.1.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: b24api
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.1.0
|
|
4
4
|
Summary: Bitrix24 API
|
|
5
5
|
Requires-Python: >=3.12
|
|
6
6
|
Description-Content-Type: text/markdown
|
|
@@ -43,6 +43,15 @@ idempotent and closes active streams before the owned transport.
|
|
|
43
43
|
Use `call()` for detached decoded JSON and `call_response()` when you also need the immutable
|
|
44
44
|
response envelope: `result`, `total`, `next`, timing and bounded diagnostic evidence.
|
|
45
45
|
|
|
46
|
+
Use `call_bytes()` when a successful method response is a file rather than a Bitrix JSON envelope.
|
|
47
|
+
The operation is explicit and never hides malformed JSON by falling back to bytes.
|
|
48
|
+
|
|
49
|
+
<!-- tested: tests/client_findings_3_test.py::test_binary_call_returns_every_success_byte_without_json_sniffing -->
|
|
50
|
+
```python
|
|
51
|
+
archive = await client.call_bytes(Request("example.export.download", replay_safety=ReplaySafety.SAFE))
|
|
52
|
+
payload = archive.body
|
|
53
|
+
```
|
|
54
|
+
|
|
46
55
|
<!-- tested: tests/client_v2_test.py::test_call_and_call_response_have_stable_detached_types -->
|
|
47
56
|
```python
|
|
48
57
|
from b24api import ReplaySafety
|
|
@@ -132,7 +141,7 @@ more specialized mechanics have explicit names and explicit preconditions.
|
|
|
132
141
|
|---|---|---|---|
|
|
133
142
|
| `iter_list` | The method supports ordinary offset pagination. | Pages are requested sequentially using server `next`; no separate count request is made. | Continuation and empty terminal page; add identity for duplicate detection. |
|
|
134
143
|
| `iter_list_counted` | The first response provides an exact filtered `total` and stable offset pages. | Head page is direct; all known tail offsets are grouped into physical Bitrix batches. | Exact total, ranges and identities. |
|
|
135
|
-
| `iter_list_keyset` | The method may omit `total`, but reliably supports ordering and filtering by a unique identity. | Sequential
|
|
144
|
+
| `iter_list_keyset` | The method may omit `total`, but reliably supports ordering and filtering by a unique integer identity. | Sequential by default; explicit range, partitioned, or auto execution may batch bounded work after a planning barrier. | Caller-asserted keyset contract, strict monotonic identity, bounded-plan canaries, and terminal empty confirmation. |
|
|
136
145
|
| `iter_list_cursor` | Each next request depends on a cursor from the previous response. | Sequential dependent cursor requests. | Strict unique monotonic cursor and empty terminal page. |
|
|
137
146
|
| `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. |
|
|
138
147
|
|
|
@@ -169,6 +178,24 @@ async with stream:
|
|
|
169
178
|
Without `identity`, successful exhaustion is reported as `MECHANICS_ONLY`: pagination completed,
|
|
170
179
|
but the client cannot prove that the portal did not duplicate or substitute rows.
|
|
171
180
|
|
|
181
|
+
Mapping-backed collections are explicit as well. `MAPPING_VALUES` yields values from a selected
|
|
182
|
+
mapping in insertion order; `MAPPING_VALUES_OR_EMPTY` additionally accepts only an empty terminal
|
|
183
|
+
sequence and records that degradation in the operation report.
|
|
184
|
+
|
|
185
|
+
<!-- tested: tests/client_findings_3_test.py::test_shape_rejection_is_retained_as_zero_admission_page_evidence -->
|
|
186
|
+
```python
|
|
187
|
+
from b24api import ResultCollectionShape
|
|
188
|
+
|
|
189
|
+
stream = client.iter_list(
|
|
190
|
+
Request("example.dictionary.list", replay_safety=ReplaySafety.SAFE),
|
|
191
|
+
selector=ResultSelector(("items",)),
|
|
192
|
+
collection_shape=ResultCollectionShape.MAPPING_VALUES,
|
|
193
|
+
)
|
|
194
|
+
async with stream:
|
|
195
|
+
async for value in stream:
|
|
196
|
+
consume(value)
|
|
197
|
+
```
|
|
198
|
+
|
|
172
199
|
### Counted, physically batched tail
|
|
173
200
|
|
|
174
201
|
The first direct page must contain an exact filtered `total` and, when more rows exist, `next`.
|
|
@@ -191,13 +218,23 @@ range, overlap, duplicate identity or total contradiction raises `IncompleteTrav
|
|
|
191
218
|
|
|
192
219
|
### No-count keyset
|
|
193
220
|
|
|
194
|
-
Keyset traversal
|
|
195
|
-
|
|
196
|
-
|
|
221
|
+
Keyset traversal is sequential by default, preserving the compatible request shape and first-pull
|
|
222
|
+
behavior. An explicit execution contract can instead capture both ordered boundaries, validate five
|
|
223
|
+
capability canaries, and batch numeric ranges or occupied-anchor partitions. Planning completes
|
|
224
|
+
before any row is emitted, so partial consumption still pays that barrier cost. Fast execution fails
|
|
225
|
+
synchronously when its declared controls or policy capacity are ineligible.
|
|
226
|
+
|
|
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
|
+
An advisory `total` may only raise an automatic cost estimate and never proves completion. Reports
|
|
231
|
+
record the selected strategy and reason: unbounded auto continuation has the same
|
|
232
|
+
`ordered_prefix_only` assurance as sequential traversal, while bounded plans additionally report
|
|
233
|
+
`canary_verified_bounds`.
|
|
197
234
|
|
|
198
235
|
<!-- tested: tests/client_v2_test.py::test_keyset_and_cursor_are_explicit_strict_alternatives -->
|
|
199
236
|
```python
|
|
200
|
-
from b24api import KeysetSpec, ParameterPath
|
|
237
|
+
from b24api import AutoKeysetExecution, KeysetSpec, ParameterPath, StableIntegerKeysetContract
|
|
201
238
|
|
|
202
239
|
stream = client.iter_list_keyset(
|
|
203
240
|
Request("example.item.list", replay_safety=ReplaySafety.SAFE),
|
|
@@ -207,6 +244,7 @@ stream = client.iter_list_keyset(
|
|
|
207
244
|
filter_path=ParameterPath(("filter",)),
|
|
208
245
|
order_path=ParameterPath(("order",)),
|
|
209
246
|
),
|
|
247
|
+
execution=AutoKeysetExecution(contract=StableIntegerKeysetContract()),
|
|
210
248
|
)
|
|
211
249
|
```
|
|
212
250
|
|
|
@@ -235,6 +273,10 @@ stream = client.iter_list_cursor(
|
|
|
235
273
|
Cursor values must be unique and strictly monotonic. If an endpoint exposes only a non-unique
|
|
236
274
|
boundary, use an application-owned direct-call workflow or supply a unique tie-breaker.
|
|
237
275
|
|
|
276
|
+
See [architecture](docs/architecture.md), [migration](docs/migration.md),
|
|
277
|
+
[performance](docs/performance.md), and [endpoint recipes](docs/recipes.md) for the complete
|
|
278
|
+
contracts and selection guidance.
|
|
279
|
+
|
|
238
280
|
### One list method across many parent entities
|
|
239
281
|
|
|
240
282
|
`Binding` applies exact parameter updates to a base request and carries parent correlation. The
|
|
@@ -31,6 +31,15 @@ idempotent and closes active streams before the owned transport.
|
|
|
31
31
|
Use `call()` for detached decoded JSON and `call_response()` when you also need the immutable
|
|
32
32
|
response envelope: `result`, `total`, `next`, timing and bounded diagnostic evidence.
|
|
33
33
|
|
|
34
|
+
Use `call_bytes()` when a successful method response is a file rather than a Bitrix JSON envelope.
|
|
35
|
+
The operation is explicit and never hides malformed JSON by falling back to bytes.
|
|
36
|
+
|
|
37
|
+
<!-- tested: tests/client_findings_3_test.py::test_binary_call_returns_every_success_byte_without_json_sniffing -->
|
|
38
|
+
```python
|
|
39
|
+
archive = await client.call_bytes(Request("example.export.download", replay_safety=ReplaySafety.SAFE))
|
|
40
|
+
payload = archive.body
|
|
41
|
+
```
|
|
42
|
+
|
|
34
43
|
<!-- tested: tests/client_v2_test.py::test_call_and_call_response_have_stable_detached_types -->
|
|
35
44
|
```python
|
|
36
45
|
from b24api import ReplaySafety
|
|
@@ -120,7 +129,7 @@ more specialized mechanics have explicit names and explicit preconditions.
|
|
|
120
129
|
|---|---|---|---|
|
|
121
130
|
| `iter_list` | The method supports ordinary offset pagination. | Pages are requested sequentially using server `next`; no separate count request is made. | Continuation and empty terminal page; add identity for duplicate detection. |
|
|
122
131
|
| `iter_list_counted` | The first response provides an exact filtered `total` and stable offset pages. | Head page is direct; all known tail offsets are grouped into physical Bitrix batches. | Exact total, ranges and identities. |
|
|
123
|
-
| `iter_list_keyset` | The method may omit `total`, but reliably supports ordering and filtering by a unique identity. | Sequential
|
|
132
|
+
| `iter_list_keyset` | The method may omit `total`, but reliably supports ordering and filtering by a unique integer identity. | Sequential by default; explicit range, partitioned, or auto execution may batch bounded work after a planning barrier. | Caller-asserted keyset contract, strict monotonic identity, bounded-plan canaries, and terminal empty confirmation. |
|
|
124
133
|
| `iter_list_cursor` | Each next request depends on a cursor from the previous response. | Sequential dependent cursor requests. | Strict unique monotonic cursor and empty terminal page. |
|
|
125
134
|
| `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. |
|
|
126
135
|
|
|
@@ -157,6 +166,24 @@ async with stream:
|
|
|
157
166
|
Without `identity`, successful exhaustion is reported as `MECHANICS_ONLY`: pagination completed,
|
|
158
167
|
but the client cannot prove that the portal did not duplicate or substitute rows.
|
|
159
168
|
|
|
169
|
+
Mapping-backed collections are explicit as well. `MAPPING_VALUES` yields values from a selected
|
|
170
|
+
mapping in insertion order; `MAPPING_VALUES_OR_EMPTY` additionally accepts only an empty terminal
|
|
171
|
+
sequence and records that degradation in the operation report.
|
|
172
|
+
|
|
173
|
+
<!-- tested: tests/client_findings_3_test.py::test_shape_rejection_is_retained_as_zero_admission_page_evidence -->
|
|
174
|
+
```python
|
|
175
|
+
from b24api import ResultCollectionShape
|
|
176
|
+
|
|
177
|
+
stream = client.iter_list(
|
|
178
|
+
Request("example.dictionary.list", replay_safety=ReplaySafety.SAFE),
|
|
179
|
+
selector=ResultSelector(("items",)),
|
|
180
|
+
collection_shape=ResultCollectionShape.MAPPING_VALUES,
|
|
181
|
+
)
|
|
182
|
+
async with stream:
|
|
183
|
+
async for value in stream:
|
|
184
|
+
consume(value)
|
|
185
|
+
```
|
|
186
|
+
|
|
160
187
|
### Counted, physically batched tail
|
|
161
188
|
|
|
162
189
|
The first direct page must contain an exact filtered `total` and, when more rows exist, `next`.
|
|
@@ -179,13 +206,23 @@ range, overlap, duplicate identity or total contradiction raises `IncompleteTrav
|
|
|
179
206
|
|
|
180
207
|
### No-count keyset
|
|
181
208
|
|
|
182
|
-
Keyset traversal
|
|
183
|
-
|
|
184
|
-
|
|
209
|
+
Keyset traversal is sequential by default, preserving the compatible request shape and first-pull
|
|
210
|
+
behavior. An explicit execution contract can instead capture both ordered boundaries, validate five
|
|
211
|
+
capability canaries, and batch numeric ranges or occupied-anchor partitions. Planning completes
|
|
212
|
+
before any row is emitted, so partial consumption still pays that barrier cost. Fast execution fails
|
|
213
|
+
synchronously when its declared controls or policy capacity are ineligible.
|
|
214
|
+
|
|
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
|
+
An advisory `total` may only raise an automatic cost estimate and never proves completion. Reports
|
|
219
|
+
record the selected strategy and reason: unbounded auto continuation has the same
|
|
220
|
+
`ordered_prefix_only` assurance as sequential traversal, while bounded plans additionally report
|
|
221
|
+
`canary_verified_bounds`.
|
|
185
222
|
|
|
186
223
|
<!-- tested: tests/client_v2_test.py::test_keyset_and_cursor_are_explicit_strict_alternatives -->
|
|
187
224
|
```python
|
|
188
|
-
from b24api import KeysetSpec, ParameterPath
|
|
225
|
+
from b24api import AutoKeysetExecution, KeysetSpec, ParameterPath, StableIntegerKeysetContract
|
|
189
226
|
|
|
190
227
|
stream = client.iter_list_keyset(
|
|
191
228
|
Request("example.item.list", replay_safety=ReplaySafety.SAFE),
|
|
@@ -195,6 +232,7 @@ stream = client.iter_list_keyset(
|
|
|
195
232
|
filter_path=ParameterPath(("filter",)),
|
|
196
233
|
order_path=ParameterPath(("order",)),
|
|
197
234
|
),
|
|
235
|
+
execution=AutoKeysetExecution(contract=StableIntegerKeysetContract()),
|
|
198
236
|
)
|
|
199
237
|
```
|
|
200
238
|
|
|
@@ -223,6 +261,10 @@ stream = client.iter_list_cursor(
|
|
|
223
261
|
Cursor values must be unique and strictly monotonic. If an endpoint exposes only a non-unique
|
|
224
262
|
boundary, use an application-owned direct-call workflow or supply a unique tie-breaker.
|
|
225
263
|
|
|
264
|
+
See [architecture](docs/architecture.md), [migration](docs/migration.md),
|
|
265
|
+
[performance](docs/performance.md), and [endpoint recipes](docs/recipes.md) for the complete
|
|
266
|
+
contracts and selection guidance.
|
|
267
|
+
|
|
226
268
|
### One list method across many parent entities
|
|
227
269
|
|
|
228
270
|
`Binding` applies exact parameter updates to a base request and carries parent correlation. The
|
|
@@ -2,14 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
from b24api.client import Bitrix24
|
|
4
4
|
from b24api.contracts import (
|
|
5
|
+
AmbiguityPolicy,
|
|
6
|
+
AmbiguityReason,
|
|
7
|
+
AutoKeysetExecution,
|
|
5
8
|
BatchDispatch,
|
|
9
|
+
BinaryEvidence,
|
|
10
|
+
BinaryResponse,
|
|
6
11
|
Binding,
|
|
12
|
+
BodyEncoding,
|
|
13
|
+
ClosureWitness,
|
|
7
14
|
Command,
|
|
8
15
|
CommandFailure,
|
|
9
16
|
CommandNotExecuted,
|
|
10
17
|
CommandOutcome,
|
|
11
18
|
CommandOutcomeUnknown,
|
|
12
19
|
CommandSuccess,
|
|
20
|
+
CompositeIdentitySpec,
|
|
21
|
+
ConsistencyPolicy,
|
|
13
22
|
CountedTraversal,
|
|
14
23
|
CursorSpec,
|
|
15
24
|
CursorTraversal,
|
|
@@ -17,16 +26,31 @@ from b24api.contracts import (
|
|
|
17
26
|
DirectDispatch,
|
|
18
27
|
ExecutionPolicy,
|
|
19
28
|
IdentityCoercion,
|
|
29
|
+
IdentityComponent,
|
|
20
30
|
IdentitySpec,
|
|
31
|
+
KeysetAssuranceSource,
|
|
32
|
+
KeysetExecution,
|
|
33
|
+
KeysetExecutionKind,
|
|
34
|
+
KeysetExecutionReport,
|
|
35
|
+
KeysetPageCompletion,
|
|
36
|
+
KeysetPhase,
|
|
37
|
+
KeysetSelectionReason,
|
|
21
38
|
KeysetSpec,
|
|
22
39
|
KeysetTraversal,
|
|
23
40
|
NotExecutedReason,
|
|
41
|
+
OffsetContinuation,
|
|
24
42
|
OffsetSpec,
|
|
25
43
|
OperationReport,
|
|
26
44
|
OperationStream,
|
|
45
|
+
PageDispatch,
|
|
46
|
+
PageOutcome,
|
|
47
|
+
PageRecord,
|
|
48
|
+
PageRejectionCode,
|
|
27
49
|
ParameterPath,
|
|
28
50
|
ParameterUpdate,
|
|
29
51
|
PartialResult,
|
|
52
|
+
PartitionedKeysetExecution,
|
|
53
|
+
RangeKeysetExecution,
|
|
30
54
|
ReferenceComplete,
|
|
31
55
|
ReferenceEvent,
|
|
32
56
|
ReferenceFailure,
|
|
@@ -36,16 +60,31 @@ from b24api.contracts import (
|
|
|
36
60
|
ReferenceOutcomeUnknown,
|
|
37
61
|
ReplaySafety,
|
|
38
62
|
Request,
|
|
63
|
+
RequestHeaders,
|
|
64
|
+
RequestSummary,
|
|
39
65
|
Response,
|
|
40
66
|
ResultCollectionShape,
|
|
67
|
+
ResultErrorShape,
|
|
68
|
+
ResultErrorSpec,
|
|
41
69
|
ResultSelector,
|
|
70
|
+
RetryPolicy,
|
|
71
|
+
SequentialKeysetExecution,
|
|
42
72
|
SequentialTraversal,
|
|
73
|
+
SplitOrderSpec,
|
|
74
|
+
StableIntegerKeysetContract,
|
|
43
75
|
TerminalState,
|
|
76
|
+
TotalHintMode,
|
|
77
|
+
TotalTermination,
|
|
78
|
+
TraceClass,
|
|
44
79
|
TraversalAssurance,
|
|
80
|
+
TraversalIdentity,
|
|
81
|
+
UnknownRequestAudit,
|
|
82
|
+
UnknownRequestCollector,
|
|
45
83
|
Violation,
|
|
46
84
|
ViolationSeverity,
|
|
47
85
|
partition_command_outcomes,
|
|
48
86
|
partition_reference_outcomes,
|
|
87
|
+
traversal_control_paths,
|
|
49
88
|
)
|
|
50
89
|
from b24api.errors import (
|
|
51
90
|
AmbiguousExecutionError,
|
|
@@ -55,57 +94,86 @@ from b24api.errors import (
|
|
|
55
94
|
BatchFailed,
|
|
56
95
|
BudgetExceededError,
|
|
57
96
|
CapabilityError,
|
|
97
|
+
EnvelopeContractError,
|
|
58
98
|
HTTPGatewayError,
|
|
99
|
+
IdentityContractError,
|
|
59
100
|
IncompleteTraversalError,
|
|
60
101
|
InputSourceError,
|
|
61
102
|
PaginationError,
|
|
62
103
|
ProtocolError,
|
|
63
104
|
ReferenceFailed,
|
|
64
105
|
ResponseTooLargeError,
|
|
106
|
+
ResultShapeError,
|
|
65
107
|
TransportError,
|
|
66
108
|
)
|
|
67
109
|
from b24api.settings import Settings
|
|
68
|
-
from b24api.transport import Transport, WireResponse
|
|
110
|
+
from b24api.transport import Transport, TransportCapabilities, WireRequest, WireResponse, WireTransport
|
|
69
111
|
|
|
70
112
|
__all__ = [
|
|
113
|
+
"AmbiguityPolicy",
|
|
114
|
+
"AmbiguityReason",
|
|
71
115
|
"AmbiguousExecutionError",
|
|
72
116
|
"ApiResponseError",
|
|
117
|
+
"AutoKeysetExecution",
|
|
73
118
|
"B24ApiError",
|
|
74
119
|
"BatchCommandError",
|
|
75
120
|
"BatchDispatch",
|
|
76
121
|
"BatchFailed",
|
|
122
|
+
"BinaryEvidence",
|
|
123
|
+
"BinaryResponse",
|
|
77
124
|
"Binding",
|
|
78
125
|
"Bitrix24",
|
|
126
|
+
"BodyEncoding",
|
|
79
127
|
"BudgetExceededError",
|
|
80
128
|
"CapabilityError",
|
|
129
|
+
"ClosureWitness",
|
|
81
130
|
"Command",
|
|
82
131
|
"CommandFailure",
|
|
83
132
|
"CommandNotExecuted",
|
|
84
133
|
"CommandOutcome",
|
|
85
134
|
"CommandOutcomeUnknown",
|
|
86
135
|
"CommandSuccess",
|
|
136
|
+
"CompositeIdentitySpec",
|
|
137
|
+
"ConsistencyPolicy",
|
|
87
138
|
"CountedTraversal",
|
|
88
139
|
"CursorSpec",
|
|
89
140
|
"CursorTraversal",
|
|
90
141
|
"DeliveryOrder",
|
|
91
142
|
"DirectDispatch",
|
|
143
|
+
"EnvelopeContractError",
|
|
92
144
|
"ExecutionPolicy",
|
|
93
145
|
"HTTPGatewayError",
|
|
94
146
|
"IdentityCoercion",
|
|
147
|
+
"IdentityComponent",
|
|
148
|
+
"IdentityContractError",
|
|
95
149
|
"IdentitySpec",
|
|
96
150
|
"IncompleteTraversalError",
|
|
97
151
|
"InputSourceError",
|
|
152
|
+
"KeysetAssuranceSource",
|
|
153
|
+
"KeysetExecution",
|
|
154
|
+
"KeysetExecutionKind",
|
|
155
|
+
"KeysetExecutionReport",
|
|
156
|
+
"KeysetPageCompletion",
|
|
157
|
+
"KeysetPhase",
|
|
158
|
+
"KeysetSelectionReason",
|
|
98
159
|
"KeysetSpec",
|
|
99
160
|
"KeysetTraversal",
|
|
100
161
|
"NotExecutedReason",
|
|
162
|
+
"OffsetContinuation",
|
|
101
163
|
"OffsetSpec",
|
|
102
164
|
"OperationReport",
|
|
103
165
|
"OperationStream",
|
|
166
|
+
"PageDispatch",
|
|
167
|
+
"PageOutcome",
|
|
168
|
+
"PageRecord",
|
|
169
|
+
"PageRejectionCode",
|
|
104
170
|
"PaginationError",
|
|
105
171
|
"ParameterPath",
|
|
106
172
|
"ParameterUpdate",
|
|
107
173
|
"PartialResult",
|
|
174
|
+
"PartitionedKeysetExecution",
|
|
108
175
|
"ProtocolError",
|
|
176
|
+
"RangeKeysetExecution",
|
|
109
177
|
"ReferenceComplete",
|
|
110
178
|
"ReferenceEvent",
|
|
111
179
|
"ReferenceFailed",
|
|
@@ -116,19 +184,38 @@ __all__ = [
|
|
|
116
184
|
"ReferenceOutcomeUnknown",
|
|
117
185
|
"ReplaySafety",
|
|
118
186
|
"Request",
|
|
187
|
+
"RequestHeaders",
|
|
188
|
+
"RequestSummary",
|
|
119
189
|
"Response",
|
|
120
190
|
"ResponseTooLargeError",
|
|
121
191
|
"ResultCollectionShape",
|
|
192
|
+
"ResultErrorShape",
|
|
193
|
+
"ResultErrorSpec",
|
|
122
194
|
"ResultSelector",
|
|
195
|
+
"ResultShapeError",
|
|
196
|
+
"RetryPolicy",
|
|
197
|
+
"SequentialKeysetExecution",
|
|
123
198
|
"SequentialTraversal",
|
|
124
199
|
"Settings",
|
|
200
|
+
"SplitOrderSpec",
|
|
201
|
+
"StableIntegerKeysetContract",
|
|
125
202
|
"TerminalState",
|
|
203
|
+
"TotalHintMode",
|
|
204
|
+
"TotalTermination",
|
|
205
|
+
"TraceClass",
|
|
126
206
|
"Transport",
|
|
207
|
+
"TransportCapabilities",
|
|
127
208
|
"TransportError",
|
|
128
209
|
"TraversalAssurance",
|
|
210
|
+
"TraversalIdentity",
|
|
211
|
+
"UnknownRequestAudit",
|
|
212
|
+
"UnknownRequestCollector",
|
|
129
213
|
"Violation",
|
|
130
214
|
"ViolationSeverity",
|
|
215
|
+
"WireRequest",
|
|
131
216
|
"WireResponse",
|
|
217
|
+
"WireTransport",
|
|
132
218
|
"partition_command_outcomes",
|
|
133
219
|
"partition_reference_outcomes",
|
|
220
|
+
"traversal_control_paths",
|
|
134
221
|
]
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"""Lazy admission-time observation for logical command sources."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from collections.abc import AsyncIterable, AsyncIterator, Callable, Iterable, Iterator
|
|
5
|
+
from typing import TYPE_CHECKING, Protocol, Self, runtime_checkable
|
|
6
|
+
|
|
7
|
+
from b24api.contracts.command import Command
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from b24api.contracts.report import Violation
|
|
11
|
+
from b24api.contracts.request import Request
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@runtime_checkable
|
|
15
|
+
class _SyncClosable(Protocol):
|
|
16
|
+
def close(self) -> None: ...
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@runtime_checkable
|
|
20
|
+
class _AsyncClosable(Protocol):
|
|
21
|
+
async def aclose(self) -> None: ...
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class _SyncAuditSource[C](Iterator[Command[C]]):
|
|
25
|
+
def __init__(self, source: Iterable[Command[C]], audit: Callable[[Request], Violation | None]) -> None:
|
|
26
|
+
self._iterator = iter(source)
|
|
27
|
+
self._audit = audit
|
|
28
|
+
self.violations: list[Violation] = []
|
|
29
|
+
|
|
30
|
+
def __iter__(self) -> Self:
|
|
31
|
+
return self
|
|
32
|
+
|
|
33
|
+
def __next__(self) -> Command[C]:
|
|
34
|
+
command = next(self._iterator)
|
|
35
|
+
if isinstance(command, Command):
|
|
36
|
+
violation = self._audit(command.request)
|
|
37
|
+
if violation is not None:
|
|
38
|
+
self.violations.append(violation)
|
|
39
|
+
return command
|
|
40
|
+
|
|
41
|
+
def close(self) -> None:
|
|
42
|
+
if isinstance(self._iterator, _SyncClosable):
|
|
43
|
+
self._iterator.close()
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class _AsyncAuditSource[C](AsyncIterator[Command[C]]):
|
|
47
|
+
def __init__(self, source: AsyncIterable[Command[C]], audit: Callable[[Request], Violation | None]) -> None:
|
|
48
|
+
self._iterator = aiter(source)
|
|
49
|
+
self._audit = audit
|
|
50
|
+
self.violations: list[Violation] = []
|
|
51
|
+
|
|
52
|
+
def __aiter__(self) -> Self:
|
|
53
|
+
return self
|
|
54
|
+
|
|
55
|
+
async def __anext__(self) -> Command[C]:
|
|
56
|
+
command = await anext(self._iterator)
|
|
57
|
+
if isinstance(command, Command):
|
|
58
|
+
violation = self._audit(command.request)
|
|
59
|
+
if violation is not None:
|
|
60
|
+
self.violations.append(violation)
|
|
61
|
+
return command
|
|
62
|
+
|
|
63
|
+
async def aclose(self) -> None:
|
|
64
|
+
if isinstance(self._iterator, _AsyncClosable):
|
|
65
|
+
await self._iterator.aclose()
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def audit_command_source[C](
|
|
69
|
+
source: Iterable[Command[C]] | AsyncIterable[Command[C]],
|
|
70
|
+
audit: Callable[[Request], Violation | None],
|
|
71
|
+
) -> Iterable[Command[C]] | AsyncIterable[Command[C]]:
|
|
72
|
+
"""Observe each command exactly when its lazy source admits it."""
|
|
73
|
+
if isinstance(source, AsyncIterable):
|
|
74
|
+
return _AsyncAuditSource(source, audit)
|
|
75
|
+
return _SyncAuditSource(source, audit)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
__all__ = ["audit_command_source"]
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Stateless traversal operation helpers for the public facade."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from typing import TYPE_CHECKING
|
|
5
|
+
|
|
6
|
+
from b24api.contracts.keyset_execution import KeysetExecution, SequentialKeysetExecution
|
|
7
|
+
from b24api.contracts.request import IdentitySpec, RequestLike, ResultSelector, TraversalIdentity, canonical_request
|
|
8
|
+
from b24api.contracts.response import ResultCollectionShape
|
|
9
|
+
from b24api.contracts.traversal import CursorSpec, KeysetSpec, OffsetSpec, TotalTermination
|
|
10
|
+
from b24api.traversal.facade import counted_stream, cursor_stream, keyset_stream, sequential_stream
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from b24api.contracts.json import JsonValue
|
|
14
|
+
from b24api.contracts.policy import ExecutionPolicy
|
|
15
|
+
from b24api.contracts.report import Violation
|
|
16
|
+
from b24api.contracts.request import Request
|
|
17
|
+
from b24api.contracts.stream import OperationStream
|
|
18
|
+
from b24api.execution import Executor
|
|
19
|
+
|
|
20
|
+
_ROOT_SELECTOR = ResultSelector.root()
|
|
21
|
+
_DEFAULT_OFFSET = OffsetSpec()
|
|
22
|
+
_DEFAULT_COUNTED_OFFSET = OffsetSpec(total_termination=TotalTermination.EXACT_QUALIFIED)
|
|
23
|
+
_DEFAULT_KEYSET = KeysetSpec()
|
|
24
|
+
_DEFAULT_SEQUENTIAL_KEYSET_EXECUTION = SequentialKeysetExecution()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class _TraversalFacade:
|
|
28
|
+
"""Traversal methods sharing the lifecycle owned by ``Bitrix24``."""
|
|
29
|
+
|
|
30
|
+
_executor: Executor
|
|
31
|
+
_default_policy: ExecutionPolicy
|
|
32
|
+
|
|
33
|
+
def _require_open(self) -> None:
|
|
34
|
+
raise NotImplementedError
|
|
35
|
+
|
|
36
|
+
def _audit_unknown(self, request: Request) -> Violation | None:
|
|
37
|
+
raise NotImplementedError
|
|
38
|
+
|
|
39
|
+
def _register_stream[T](self, stream: OperationStream[T]) -> OperationStream[T]:
|
|
40
|
+
raise NotImplementedError
|
|
41
|
+
|
|
42
|
+
def _discard_stream(self, stream: object) -> None:
|
|
43
|
+
raise NotImplementedError
|
|
44
|
+
|
|
45
|
+
def iter_list( # noqa: PLR0913
|
|
46
|
+
self,
|
|
47
|
+
request: RequestLike,
|
|
48
|
+
*,
|
|
49
|
+
selector: ResultSelector = _ROOT_SELECTOR,
|
|
50
|
+
identity: TraversalIdentity | None = None,
|
|
51
|
+
collection_shape: ResultCollectionShape = ResultCollectionShape.SEQUENCE,
|
|
52
|
+
page_size: int = 50,
|
|
53
|
+
offset: OffsetSpec = _DEFAULT_OFFSET,
|
|
54
|
+
policy: ExecutionPolicy | None = None,
|
|
55
|
+
) -> OperationStream[JsonValue]:
|
|
56
|
+
"""Return conservative sequential offset/server-next traversal."""
|
|
57
|
+
self._require_open()
|
|
58
|
+
canonical = canonical_request(request)
|
|
59
|
+
audit_violation = self._audit_unknown(canonical)
|
|
60
|
+
return self._register_stream(
|
|
61
|
+
sequential_stream(
|
|
62
|
+
self._executor,
|
|
63
|
+
canonical,
|
|
64
|
+
selector=selector,
|
|
65
|
+
identity=identity,
|
|
66
|
+
collection_shape=collection_shape,
|
|
67
|
+
page_size=page_size,
|
|
68
|
+
offset=offset,
|
|
69
|
+
policy=policy or self._default_policy,
|
|
70
|
+
deregister=self._discard_stream,
|
|
71
|
+
audit_violations=(() if audit_violation is None else (audit_violation,)),
|
|
72
|
+
),
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def iter_list_counted( # noqa: PLR0913
|
|
76
|
+
self,
|
|
77
|
+
request: RequestLike,
|
|
78
|
+
*,
|
|
79
|
+
identity: TraversalIdentity | None = None,
|
|
80
|
+
selector: ResultSelector = _ROOT_SELECTOR,
|
|
81
|
+
collection_shape: ResultCollectionShape = ResultCollectionShape.SEQUENCE,
|
|
82
|
+
page_size: int = 50,
|
|
83
|
+
batch_size: int | None = None,
|
|
84
|
+
offset: OffsetSpec = _DEFAULT_COUNTED_OFFSET,
|
|
85
|
+
policy: ExecutionPolicy | None = None,
|
|
86
|
+
) -> OperationStream[JsonValue]:
|
|
87
|
+
"""Return exact direct-head plus physically batched counted traversal."""
|
|
88
|
+
self._require_open()
|
|
89
|
+
canonical = canonical_request(request)
|
|
90
|
+
audit_violation = self._audit_unknown(canonical)
|
|
91
|
+
return self._register_stream(
|
|
92
|
+
counted_stream(
|
|
93
|
+
self._executor,
|
|
94
|
+
canonical,
|
|
95
|
+
identity=identity,
|
|
96
|
+
selector=selector,
|
|
97
|
+
collection_shape=collection_shape,
|
|
98
|
+
page_size=page_size,
|
|
99
|
+
batch_size=batch_size,
|
|
100
|
+
offset=offset,
|
|
101
|
+
policy=policy or self._default_policy,
|
|
102
|
+
deregister=self._discard_stream,
|
|
103
|
+
audit_violations=(() if audit_violation is None else (audit_violation,)),
|
|
104
|
+
),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
def iter_list_keyset( # noqa: PLR0913
|
|
108
|
+
self,
|
|
109
|
+
request: RequestLike,
|
|
110
|
+
*,
|
|
111
|
+
selector: ResultSelector,
|
|
112
|
+
identity: IdentitySpec,
|
|
113
|
+
collection_shape: ResultCollectionShape = ResultCollectionShape.SEQUENCE,
|
|
114
|
+
page_size: int = 50,
|
|
115
|
+
keyset: KeysetSpec = _DEFAULT_KEYSET,
|
|
116
|
+
execution: KeysetExecution = _DEFAULT_SEQUENTIAL_KEYSET_EXECUTION,
|
|
117
|
+
policy: ExecutionPolicy | None = None,
|
|
118
|
+
) -> OperationStream[JsonValue]:
|
|
119
|
+
"""Return exact sequential no-count keyset traversal."""
|
|
120
|
+
self._require_open()
|
|
121
|
+
canonical = canonical_request(request)
|
|
122
|
+
audit_violation = self._audit_unknown(canonical)
|
|
123
|
+
return self._register_stream(
|
|
124
|
+
keyset_stream(
|
|
125
|
+
self._executor,
|
|
126
|
+
canonical,
|
|
127
|
+
selector=selector,
|
|
128
|
+
identity=identity,
|
|
129
|
+
collection_shape=collection_shape,
|
|
130
|
+
page_size=page_size,
|
|
131
|
+
keyset=keyset,
|
|
132
|
+
execution=execution,
|
|
133
|
+
policy=policy or self._default_policy,
|
|
134
|
+
deregister=self._discard_stream,
|
|
135
|
+
audit_violations=(() if audit_violation is None else (audit_violation,)),
|
|
136
|
+
),
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def iter_list_cursor( # noqa: PLR0913
|
|
140
|
+
self,
|
|
141
|
+
request: RequestLike,
|
|
142
|
+
*,
|
|
143
|
+
selector: ResultSelector,
|
|
144
|
+
cursor: CursorSpec,
|
|
145
|
+
identity: IdentitySpec | None = None,
|
|
146
|
+
collection_shape: ResultCollectionShape = ResultCollectionShape.SEQUENCE,
|
|
147
|
+
page_size: int = 50,
|
|
148
|
+
policy: ExecutionPolicy | None = None,
|
|
149
|
+
) -> OperationStream[JsonValue]:
|
|
150
|
+
"""Return strict dependent cursor traversal with empty confirmation."""
|
|
151
|
+
self._require_open()
|
|
152
|
+
canonical = canonical_request(request)
|
|
153
|
+
audit_violation = self._audit_unknown(canonical)
|
|
154
|
+
return self._register_stream(
|
|
155
|
+
cursor_stream(
|
|
156
|
+
self._executor,
|
|
157
|
+
canonical,
|
|
158
|
+
selector=selector,
|
|
159
|
+
cursor=cursor,
|
|
160
|
+
identity=identity,
|
|
161
|
+
collection_shape=collection_shape,
|
|
162
|
+
page_size=page_size,
|
|
163
|
+
policy=policy or self._default_policy,
|
|
164
|
+
deregister=self._discard_stream,
|
|
165
|
+
audit_violations=(() if audit_violation is None else (audit_violation,)),
|
|
166
|
+
),
|
|
167
|
+
)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
__all__: list[str] = []
|