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.
Files changed (156) hide show
  1. {b24api-2.3.0 → b24api-2.4.2}/PKG-INFO +120 -37
  2. b24api-2.3.0/b24api.egg-info/PKG-INFO → b24api-2.4.2/README.md +99 -47
  3. b24api-2.4.2/b24api/__init__.py +134 -0
  4. b24api-2.4.2/b24api/_diagnostics.py +138 -0
  5. b24api-2.4.2/b24api/_sources.py +172 -0
  6. {b24api-2.3.0 → b24api-2.4.2}/b24api/batch/engine.py +190 -44
  7. {b24api-2.3.0 → b24api-2.4.2}/b24api/batch/facade.py +11 -12
  8. b24api-2.4.2/b24api/batch/logical.py +396 -0
  9. b24api-2.4.2/b24api/batch/stream.py +272 -0
  10. {b24api-2.3.0 → b24api-2.4.2}/b24api/cli.py +3 -1
  11. {b24api-2.3.0 → b24api-2.4.2}/b24api/cli_contract.py +12 -7
  12. {b24api-2.3.0 → b24api-2.4.2}/b24api/client.py +40 -15
  13. {b24api-2.3.0 → b24api-2.4.2}/b24api/completion/fast_recorder.py +7 -18
  14. {b24api-2.3.0 → b24api-2.4.2}/b24api/completion/gate.py +257 -158
  15. b24api-2.4.2/b24api/completion/operation_stream.py +294 -0
  16. {b24api-2.3.0 → b24api-2.4.2}/b24api/completion/recorder.py +4 -13
  17. {b24api-2.3.0 → b24api-2.4.2}/b24api/completion/reference_recorder.py +3 -11
  18. {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/__init__.py +3 -2
  19. {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/command.py +3 -3
  20. {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/completion.py +16 -1
  21. {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/dispatch.py +5 -4
  22. b24api-2.4.2/b24api/contracts/error_base.py +75 -0
  23. b24api-2.4.2/b24api/contracts/evidence.py +41 -0
  24. {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/keyset_execution.py +4 -2
  25. {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/policy.py +29 -11
  26. {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/positional.py +24 -6
  27. {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/reference.py +1 -1
  28. {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/report.py +39 -15
  29. {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/request.py +75 -53
  30. b24api-2.4.2/b24api/contracts/request_summary.py +63 -0
  31. {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/response.py +6 -33
  32. b24api-2.4.2/b24api/contracts/v3_codes.py +61 -0
  33. {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/violation.py +3 -7
  34. {b24api-2.3.0 → b24api-2.4.2}/b24api/errors.py +57 -90
  35. b24api-2.4.2/b24api/execution/boundary.py +80 -0
  36. {b24api-2.3.0 → b24api-2.4.2}/b24api/execution/cleanup.py +15 -12
  37. {b24api-2.3.0 → b24api-2.4.2}/b24api/execution/context.py +90 -70
  38. {b24api-2.3.0 → b24api-2.4.2}/b24api/execution/executor.py +238 -145
  39. {b24api-2.3.0 → b24api-2.4.2}/b24api/execution/failure.py +62 -3
  40. b24api-2.4.2/b24api/execution/lifecycle.py +400 -0
  41. {b24api-2.3.0 → b24api-2.4.2}/b24api/execution/rate.py +93 -88
  42. {b24api-2.3.0 → b24api-2.4.2}/b24api/execution/snapshot.py +8 -4
  43. b24api-2.4.2/b24api/execution/throttle.py +107 -0
  44. b24api-2.4.2/b24api/migration.py +260 -0
  45. b24api-2.4.2/b24api/py.typed +0 -0
  46. b24api-2.4.2/b24api/redaction.py +339 -0
  47. {b24api-2.3.0 → b24api-2.4.2}/b24api/references/binding.py +27 -114
  48. {b24api-2.3.0 → b24api-2.4.2}/b24api/references/dispatch.py +34 -31
  49. {b24api-2.3.0 → b24api-2.4.2}/b24api/references/dispatch_plan.py +2 -3
  50. {b24api-2.3.0 → b24api-2.4.2}/b24api/references/facade.py +17 -11
  51. {b24api-2.3.0 → b24api-2.4.2}/b24api/references/fanout.py +35 -82
  52. {b24api-2.3.0 → b24api-2.4.2}/b24api/references/outcome.py +4 -7
  53. {b24api-2.3.0 → b24api-2.4.2}/b24api/references/scheduler.py +211 -231
  54. b24api-2.4.2/b24api/references/stream.py +274 -0
  55. {b24api-2.3.0 → b24api-2.4.2}/b24api/references/support.py +4 -97
  56. {b24api-2.3.0 → b24api-2.4.2}/b24api/testing/scripted.py +4 -4
  57. {b24api-2.3.0 → b24api-2.4.2}/b24api/testing/transport.py +2 -1
  58. {b24api-2.3.0 → b24api-2.4.2}/b24api/transport/base.py +2 -1
  59. b24api-2.4.2/b24api/transport/decoding.py +207 -0
  60. {b24api-2.3.0 → b24api-2.4.2}/b24api/transport/httpx.py +48 -40
  61. b24api-2.4.2/b24api/transport/log_records.py +111 -0
  62. b24api-2.4.2/b24api/transport/logging_shield.py +366 -0
  63. {b24api-2.3.0 → b24api-2.4.2}/b24api/transport/protocol.py +81 -54
  64. {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/control_preflight.py +20 -7
  65. {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/counted.py +10 -5
  66. b24api-2.4.2/b24api/traversal/counted_batch.py +369 -0
  67. b24api-2.4.2/b24api/traversal/counted_rules.py +74 -0
  68. b24api-2.4.2/b24api/traversal/cursor.py +106 -0
  69. {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/driver.py +148 -42
  70. {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/facade.py +31 -21
  71. {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/identity.py +7 -76
  72. b24api-2.4.2/b24api/traversal/keyset.py +67 -0
  73. {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/keyset_auto.py +11 -20
  74. {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/keyset_capability.py +8 -107
  75. {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/keyset_eligibility.py +12 -8
  76. {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/keyset_fast_plan.py +1 -39
  77. b24api-2.4.2/b24api/traversal/keyset_fast_stream.py +174 -0
  78. b24api-2.4.2/b24api/traversal/keyset_geometry.py +313 -0
  79. {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/keyset_observation.py +125 -85
  80. b24api-2.3.0/b24api/traversal/ordered_admission.py → b24api-2.4.2/b24api/traversal/keyset_ordered_admission.py +2 -10
  81. b24api-2.3.0/b24api/traversal/page_validation.py → b24api-2.4.2/b24api/traversal/keyset_page_validation.py +3 -9
  82. b24api-2.4.2/b24api/traversal/keyset_plan.py +453 -0
  83. b24api-2.4.2/b24api/traversal/keyset_reporting.py +96 -0
  84. b24api-2.4.2/b24api/traversal/keyset_runtime.py +413 -0
  85. b24api-2.4.2/b24api/traversal/keyset_transaction_contract.py +195 -0
  86. b24api-2.4.2/b24api/traversal/keyset_transactions.py +451 -0
  87. {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/keyset_verifier.py +35 -13
  88. b24api-2.4.2/b24api/traversal/offset_rules.py +159 -0
  89. {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/page_adaptation.py +3 -2
  90. {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/plans.py +8 -8
  91. b24api-2.4.2/b24api/traversal/sequential.py +232 -0
  92. b24api-2.4.2/b24api/traversal/strategy_context.py +181 -0
  93. b24api-2.4.2/b24api/traversal/stream.py +299 -0
  94. {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/values.py +4 -4
  95. b24api-2.3.0/README.md → b24api-2.4.2/b24api.egg-info/PKG-INFO +130 -35
  96. {b24api-2.3.0 → b24api-2.4.2}/b24api.egg-info/SOURCES.txt +20 -8
  97. {b24api-2.3.0 → b24api-2.4.2}/b24api.egg-info/requires.txt +3 -1
  98. b24api-2.4.2/pyproject.toml +132 -0
  99. b24api-2.3.0/b24api/__init__.py +0 -335
  100. b24api-2.3.0/b24api/_audit.py +0 -78
  101. b24api-2.3.0/b24api/_stream.py +0 -274
  102. b24api-2.3.0/b24api/batch/logical.py +0 -433
  103. b24api-2.3.0/b24api/batch/stream.py +0 -436
  104. b24api-2.3.0/b24api/execution/throttle.py +0 -53
  105. b24api-2.3.0/b24api/redaction.py +0 -205
  106. b24api-2.3.0/b24api/references/stream.py +0 -394
  107. b24api-2.3.0/b24api/transport/logging_shield.py +0 -244
  108. b24api-2.3.0/b24api/traversal/counted_batch.py +0 -288
  109. b24api-2.3.0/b24api/traversal/cursor.py +0 -108
  110. b24api-2.3.0/b24api/traversal/keyset.py +0 -68
  111. b24api-2.3.0/b24api/traversal/keyset_costs.py +0 -185
  112. b24api-2.3.0/b24api/traversal/keyset_fast_stream.py +0 -311
  113. b24api-2.3.0/b24api/traversal/keyset_partition.py +0 -27
  114. b24api-2.3.0/b24api/traversal/keyset_range.py +0 -80
  115. b24api-2.3.0/b24api/traversal/keyset_reporting.py +0 -111
  116. b24api-2.3.0/b24api/traversal/keyset_scheduler.py +0 -669
  117. b24api-2.3.0/b24api/traversal/keyset_transaction_contract.py +0 -108
  118. b24api-2.3.0/b24api/traversal/keyset_transactions.py +0 -418
  119. b24api-2.3.0/b24api/traversal/offset_rules.py +0 -76
  120. b24api-2.3.0/b24api/traversal/sequential.py +0 -252
  121. b24api-2.3.0/b24api/traversal/stream.py +0 -412
  122. b24api-2.3.0/pyproject.toml +0 -94
  123. {b24api-2.3.0 → b24api-2.4.2}/LICENSE +0 -0
  124. {b24api-2.3.0 → b24api-2.4.2}/MANIFEST.in +0 -0
  125. {b24api-2.3.0 → b24api-2.4.2}/b24api/_client_traversal.py +0 -0
  126. {b24api-2.3.0 → b24api-2.4.2}/b24api/_error_types.py +0 -0
  127. {b24api-2.3.0 → b24api-2.4.2}/b24api/batch/__init__.py +0 -0
  128. {b24api-2.3.0 → b24api-2.4.2}/b24api/batch/outcome.py +0 -0
  129. {b24api-2.3.0 → b24api-2.4.2}/b24api/completion/__init__.py +0 -0
  130. {b24api-2.3.0 → b24api-2.4.2}/b24api/completion/closure.py +0 -0
  131. {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/bounded_range.py +0 -0
  132. {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/identity_store.py +0 -0
  133. {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/json.py +0 -0
  134. {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/keyset_capability.py +0 -0
  135. {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/page.py +0 -0
  136. {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/page_stop.py +0 -0
  137. {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/stream.py +0 -0
  138. {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/traversal.py +0 -0
  139. {b24api-2.3.0 → b24api-2.4.2}/b24api/contracts/wire.py +0 -0
  140. {b24api-2.3.0 → b24api-2.4.2}/b24api/encoding.py +0 -0
  141. {b24api-2.3.0 → b24api-2.4.2}/b24api/execution/__init__.py +0 -0
  142. {b24api-2.3.0 → b24api-2.4.2}/b24api/references/__init__.py +0 -0
  143. {b24api-2.3.0 → b24api-2.4.2}/b24api/settings.py +0 -0
  144. {b24api-2.3.0 → b24api-2.4.2}/b24api/testing/__init__.py +0 -0
  145. {b24api-2.3.0 → b24api-2.4.2}/b24api/testing/_isolation.py +0 -0
  146. {b24api-2.3.0 → b24api-2.4.2}/b24api/transport/__init__.py +0 -0
  147. {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/__init__.py +0 -0
  148. {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/cursor_domain.py +0 -0
  149. {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/facade_support.py +0 -0
  150. {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/identity_ledger.py +0 -0
  151. {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/keyset_step.py +0 -0
  152. {b24api-2.3.0 → b24api-2.4.2}/b24api/traversal/sparse.py +0 -0
  153. {b24api-2.3.0 → b24api-2.4.2}/b24api.egg-info/dependency_links.txt +0 -0
  154. {b24api-2.3.0 → b24api-2.4.2}/b24api.egg-info/entry_points.txt +0 -0
  155. {b24api-2.3.0 → b24api-2.4.2}/b24api.egg-info/top_level.txt +0 -0
  156. {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.0
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: pydantic>=2.11.7
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 2.x
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
- <!-- tested: tests/client_v2_test.py::test_call_and_call_response_have_stable_detached_types -->
49
+ ## Quickstart
50
+
51
+ <!-- tested: tests/readme_test.py::test_quickstart_runs_exactly_against_a_scripted_portal -->
31
52
  ```python
32
- from b24api import Bitrix24, Request, RouteKind
53
+ import os
54
+
55
+ from b24api import Bitrix24, Request
33
56
 
34
- async with Bitrix24() as client:
35
- profile = await client.call(Request("profile", route=RouteKind.BARE))
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
- The client owns its default transport. An injected transport remains caller-owned. `aclose()` is
39
- idempotent and closes active streams before the owned transport.
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 proved to occur before dispatch may still be retried. Method names never imply safety;
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
- ![List traversal comparison](list-traversal-comparison.svg)
225
+ ![List traversal comparison](https://raw.githubusercontent.com/shkarupa-alex/b24api/master/list-traversal-comparison.svg)
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 now includes `keyset_execution` for omitted-execution keyset calls so consumers can
259
- see the requested and selected plan.
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
- ![Cursor batching across independent chats](cursor-batching.svg)
429
+ ![Cursor batching across independent chats](https://raw.githubusercontent.com/shkarupa-alex/b24api/master/cursor-batching.svg)
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
- ![Reference batching across leads and deals](references-batching.svg)
441
+ ![Reference batching across leads and deals](https://raw.githubusercontent.com/shkarupa-alex/b24api/master/references-batching.svg)
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
- BatchDispatch,
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
- .venv/bin/pytest -q -p no:cacheprovider
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
- Metadata-Version: 2.4
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
- <!-- tested: tests/client_v2_test.py::test_call_and_call_response_have_stable_detached_types -->
18
+ ## Quickstart
19
+
20
+ <!-- tested: tests/readme_test.py::test_quickstart_runs_exactly_against_a_scripted_portal -->
31
21
  ```python
32
- from b24api import Bitrix24, Request, RouteKind
22
+ import os
23
+
24
+ from b24api import Bitrix24, Request
33
25
 
34
- async with Bitrix24() as client:
35
- profile = await client.call(Request("profile", route=RouteKind.BARE))
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
- The client owns its default transport. An injected transport remains caller-owned. `aclose()` is
39
- idempotent and closes active streams before the owned transport.
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 proved to occur before dispatch may still be retried. Method names never imply safety;
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
- ![List traversal comparison](list-traversal-comparison.svg)
194
+ ![List traversal comparison](https://raw.githubusercontent.com/shkarupa-alex/b24api/master/list-traversal-comparison.svg)
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 now includes `keyset_execution` for omitted-execution keyset calls so consumers can
259
- see the requested and selected plan.
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
- ![Cursor batching across independent chats](cursor-batching.svg)
398
+ ![Cursor batching across independent chats](https://raw.githubusercontent.com/shkarupa-alex/b24api/master/cursor-batching.svg)
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
- ![Reference batching across leads and deals](references-batching.svg)
410
+ ![Reference batching across leads and deals](https://raw.githubusercontent.com/shkarupa-alex/b24api/master/references-batching.svg)
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
- BatchDispatch,
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
- .venv/bin/pytest -q -p no:cacheprovider
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")