coalestra 0.5.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.
Files changed (89) hide show
  1. coalestra-0.5.0/CHANGELOG.md +60 -0
  2. coalestra-0.5.0/CONTRIBUTING.md +9 -0
  3. coalestra-0.5.0/LICENSE +21 -0
  4. coalestra-0.5.0/MANIFEST.in +11 -0
  5. coalestra-0.5.0/Makefile +28 -0
  6. coalestra-0.5.0/PKG-INFO +456 -0
  7. coalestra-0.5.0/README.md +428 -0
  8. coalestra-0.5.0/benchmarks/benchmark_local_sources.py +48 -0
  9. coalestra-0.5.0/benchmarks/benchmark_snapshot.py +60 -0
  10. coalestra-0.5.0/docs/alphora-integration.pt-BR.md +400 -0
  11. coalestra-0.5.0/docs/architecture.md +240 -0
  12. coalestra-0.5.0/docs/implementation-4-5-6.pt-BR.md +70 -0
  13. coalestra-0.5.0/docs/implementation-7-8-9.pt-BR.md +86 -0
  14. coalestra-0.5.0/docs/integration-readiness.pt-BR.md +69 -0
  15. coalestra-0.5.0/docs/migration-0.3.md +74 -0
  16. coalestra-0.5.0/docs/migration-0.4.md +111 -0
  17. coalestra-0.5.0/docs/migration-0.5.md +54 -0
  18. coalestra-0.5.0/docs/public-api.md +350 -0
  19. coalestra-0.5.0/examples/alphora_adapter_example.py +145 -0
  20. coalestra-0.5.0/examples/capacity_circuit_publisher.py +61 -0
  21. coalestra-0.5.0/examples/generic_example.py +70 -0
  22. coalestra-0.5.0/examples/identity_refresh_observability.py +65 -0
  23. coalestra-0.5.0/examples/session_batch_derived.py +62 -0
  24. coalestra-0.5.0/pyproject.toml +68 -0
  25. coalestra-0.5.0/scripts/check_test_count.py +33 -0
  26. coalestra-0.5.0/setup.cfg +4 -0
  27. coalestra-0.5.0/src/coalestra/__init__.py +154 -0
  28. coalestra-0.5.0/src/coalestra/adapters/__init__.py +5 -0
  29. coalestra-0.5.0/src/coalestra/adapters/batch_source.py +94 -0
  30. coalestra-0.5.0/src/coalestra/adapters/callable_source.py +64 -0
  31. coalestra-0.5.0/src/coalestra/adapters/derived_source.py +78 -0
  32. coalestra-0.5.0/src/coalestra/cache/__init__.py +16 -0
  33. coalestra-0.5.0/src/coalestra/cache/memory.py +220 -0
  34. coalestra-0.5.0/src/coalestra/cache/publisher.py +343 -0
  35. coalestra-0.5.0/src/coalestra/concurrency/__init__.py +3 -0
  36. coalestra-0.5.0/src/coalestra/concurrency/capacity.py +137 -0
  37. coalestra-0.5.0/src/coalestra/core/__init__.py +96 -0
  38. coalestra-0.5.0/src/coalestra/core/clock.py +11 -0
  39. coalestra-0.5.0/src/coalestra/core/diagnostics.py +131 -0
  40. coalestra-0.5.0/src/coalestra/core/errors.py +92 -0
  41. coalestra-0.5.0/src/coalestra/core/health.py +23 -0
  42. coalestra-0.5.0/src/coalestra/core/keys.py +211 -0
  43. coalestra-0.5.0/src/coalestra/core/models.py +179 -0
  44. coalestra-0.5.0/src/coalestra/core/protocols.py +143 -0
  45. coalestra-0.5.0/src/coalestra/core/quality.py +19 -0
  46. coalestra-0.5.0/src/coalestra/core/request.py +45 -0
  47. coalestra-0.5.0/src/coalestra/observability/__init__.py +28 -0
  48. coalestra-0.5.0/src/coalestra/observability/buffered.py +285 -0
  49. coalestra-0.5.0/src/coalestra/observability/events.py +17 -0
  50. coalestra-0.5.0/src/coalestra/observability/metrics.py +63 -0
  51. coalestra-0.5.0/src/coalestra/orchestration/__init__.py +5 -0
  52. coalestra-0.5.0/src/coalestra/orchestration/builder.py +1621 -0
  53. coalestra-0.5.0/src/coalestra/orchestration/policy.py +26 -0
  54. coalestra-0.5.0/src/coalestra/orchestration/session.py +166 -0
  55. coalestra-0.5.0/src/coalestra/orchestration/singleflight.py +113 -0
  56. coalestra-0.5.0/src/coalestra/py.typed +0 -0
  57. coalestra-0.5.0/src/coalestra/resilience/__init__.py +25 -0
  58. coalestra-0.5.0/src/coalestra/resilience/circuit_breaker.py +228 -0
  59. coalestra-0.5.0/src/coalestra/resilience/policy.py +85 -0
  60. coalestra-0.5.0/src/coalestra/resilience/retry.py +96 -0
  61. coalestra-0.5.0/src/coalestra/sync.py +335 -0
  62. coalestra-0.5.0/src/coalestra.egg-info/PKG-INFO +456 -0
  63. coalestra-0.5.0/src/coalestra.egg-info/SOURCES.txt +87 -0
  64. coalestra-0.5.0/src/coalestra.egg-info/dependency_links.txt +1 -0
  65. coalestra-0.5.0/src/coalestra.egg-info/requires.txt +6 -0
  66. coalestra-0.5.0/src/coalestra.egg-info/top_level.txt +1 -0
  67. coalestra-0.5.0/tests/test_batch_source.py +231 -0
  68. coalestra-0.5.0/tests/test_buffered_observability_v04.py +97 -0
  69. coalestra-0.5.0/tests/test_builder.py +215 -0
  70. coalestra-0.5.0/tests/test_cache.py +61 -0
  71. coalestra-0.5.0/tests/test_cache_v04.py +128 -0
  72. coalestra-0.5.0/tests/test_callable_source.py +33 -0
  73. coalestra-0.5.0/tests/test_circuit_breaker.py +66 -0
  74. coalestra-0.5.0/tests/test_circuit_scopes.py +294 -0
  75. coalestra-0.5.0/tests/test_concurrency.py +42 -0
  76. coalestra-0.5.0/tests/test_deadline.py +48 -0
  77. coalestra-0.5.0/tests/test_derived_source.py +249 -0
  78. coalestra-0.5.0/tests/test_diagnostics_v04.py +53 -0
  79. coalestra-0.5.0/tests/test_global_capacity.py +148 -0
  80. coalestra-0.5.0/tests/test_integration_v05.py +343 -0
  81. coalestra-0.5.0/tests/test_matrix_v05.py +245 -0
  82. coalestra-0.5.0/tests/test_models.py +21 -0
  83. coalestra-0.5.0/tests/test_policy.py +21 -0
  84. coalestra-0.5.0/tests/test_publisher.py +170 -0
  85. coalestra-0.5.0/tests/test_refresh_v04.py +140 -0
  86. coalestra-0.5.0/tests/test_resource_keys_v04.py +91 -0
  87. coalestra-0.5.0/tests/test_session.py +192 -0
  88. coalestra-0.5.0/tests/test_singleflight.py +73 -0
  89. coalestra-0.5.0/tests/test_sync.py +78 -0
@@ -0,0 +1,60 @@
1
+ # Changelog
2
+
3
+ ## 0.5.0 - 2026-06-13
4
+
5
+ - Added `SnapshotRequest` with required and optional resource semantics.
6
+ - Added partial snapshots to `SnapshotBuildError` for immediate fallback and diagnostics.
7
+ - Added `SnapshotBuilder.build_request()` and synchronous/session equivalents.
8
+ - Added `BuilderHealth` and health snapshots covering cache, capacity, circuits, refreshes, and single-flight work.
9
+ - Added configurable, bounded LRU caching for `source.supports()` decisions.
10
+ - Added `max_batch_size` with capacity-aware chunk dispatch for batch sources.
11
+ - Added deadline-aware retry backoff and exact retry-attempt diagnostics.
12
+ - Added `ObservationPolicy` for future-timestamp precision in sources and event publication.
13
+ - Fixed bulk publication so the newest duplicate update wins regardless of input order.
14
+ - Preserved each `ResourceKey` normalizer across qualifier transformations.
15
+ - Added optional inline execution for guaranteed non-blocking synchronous source adapters.
16
+ - Added managed component lifecycle and corrected synchronous builder shutdown.
17
+ - Expanded snapshot diagnostics with support-cache, retry, chunk, and timestamp-rejection counters.
18
+ - Added an enforced 500-test minimum; the release suite contains 618 collected tests.
19
+
20
+ ## 0.4.0 - 2026-06-13
21
+
22
+ - Made `ResourceKey` case-preserving by default and added configurable `KeyNormalizer` policies.
23
+ - Added immutable, order-independent resource qualifiers for parameterized resources.
24
+ - Added `LEGACY_KEY_NORMALIZER` and migration helpers for 0.1-0.3 behavior.
25
+ - Added `BatchAsyncCache`, cache `get_many`/`set_many`/`invalidate_many`, namespace invalidation, pruning, and cache statistics.
26
+ - Added a bounded default memory-cache size with LRU eviction and automatic removal of fully expired entries.
27
+ - Added `RefreshMode.BLOCKING`, `STALE_WHILE_REVALIDATE`, and `REFRESH_AHEAD`.
28
+ - Added cancellation-safe background refresh scheduling, deduplication, lifecycle methods, and synchronous waiting.
29
+ - Added immutable `SnapshotDiagnostics` with per-session cache, source, batch, refresh, coalescing, latency, and observation-skew data.
30
+ - Added `BufferedEventSink` and `BufferedMetricsSink` with bounded queues and explicit overflow policies.
31
+ - Updated bulk publication to use cache batch operations under stable striped locking.
32
+ - Prevented background refreshes from replacing cache state with stale results.
33
+ - Expanded the test suite to 72 scenarios.
34
+
35
+ ## 0.2.0 - 2026-06-13
36
+
37
+ - Added `BatchSnapshotSource` and `CallableBatchSource`.
38
+ - Added partial batch results with automatic fallback for omitted resources.
39
+ - Added per-key single-flight reservations for overlapping batch requests.
40
+ - Added asynchronous `SnapshotSession` for incremental multi-stage acquisition.
41
+ - Added `SyncSnapshotSession` and `SyncSnapshotBuilder.session()`.
42
+ - Added one identity, deadline, concurrency budget, and pinned-value memo per session.
43
+ - Added explicit retry of session errors through `retry_errors=True`.
44
+ - Added `DerivedSource` and `CallableDerivedSource`.
45
+ - Added recursive dependency resolution, dependency sharing, derived-value caching, and source fallback.
46
+ - Added direct and indirect dependency-cycle detection.
47
+ - Added source protocol validation for invalid batch responses and dependencies.
48
+ - Expanded the test suite to 37 scenarios.
49
+
50
+ ## 0.1.0 - 2026-06-13
51
+
52
+ - Initial architecture.
53
+ - Concurrent snapshot construction.
54
+ - Request coalescing through single-flight execution.
55
+ - Per-resource freshness policies.
56
+ - Source priority and fallback.
57
+ - Retry and circuit-breaker policies.
58
+ - Stale-on-error support.
59
+ - In-memory metrics and structured events.
60
+ - Generic callable adapters and an Alphora integration example.
@@ -0,0 +1,9 @@
1
+ # Contributing
2
+
3
+ 1. Create a virtual environment.
4
+ 2. Install the project with `python -m pip install -e ".[dev]"`.
5
+ 3. Run `make quality` before opening a pull request.
6
+ 4. Keep the core independent from HTTP clients, exchanges, databases, and application models.
7
+ 5. Add tests for concurrency, fallback, freshness, and failure behavior.
8
+
9
+ Public APIs must remain protocol-oriented. Application-specific integrations belong in adapters or in the consuming application.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Igor Souza
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,11 @@
1
+ include LICENSE
2
+ include README.md
3
+ include CHANGELOG.md
4
+ include CONTRIBUTING.md
5
+ include Makefile
6
+ recursive-include docs *.md
7
+ recursive-include examples *.py
8
+ recursive-include benchmarks *.py
9
+ recursive-include tests *.py
10
+
11
+ recursive-include scripts *.py
@@ -0,0 +1,28 @@
1
+ .PHONY: install format lint typecheck test test-count benchmark build quality
2
+
3
+ install:
4
+ python -m pip install -e ".[dev]"
5
+
6
+ format:
7
+ python -m ruff format .
8
+
9
+ lint:
10
+ python -m ruff check .
11
+
12
+ typecheck:
13
+ python -m mypy
14
+
15
+ test:
16
+ python -m pytest
17
+
18
+ test-count:
19
+ python scripts/check_test_count.py --minimum 500
20
+
21
+ benchmark:
22
+ PYTHONPATH=src python benchmarks/benchmark_snapshot.py
23
+ PYTHONPATH=src python benchmarks/benchmark_local_sources.py
24
+
25
+ build:
26
+ python -m build
27
+
28
+ quality: format lint typecheck test-count test build
@@ -0,0 +1,456 @@
1
+ Metadata-Version: 2.4
2
+ Name: coalestra
3
+ Version: 0.5.0
4
+ Summary: Integration-ready consistent snapshots with required/optional resources, bounded batch dispatch, precise deadlines, and fast local sources.
5
+ Author: Igor Souza
6
+ License-Expression: MIT
7
+ Project-URL: Documentation, https://github.com/igors93/coalestra
8
+ Project-URL: Repository, https://github.com/igors93/coalestra
9
+ Project-URL: Issues, https://github.com/igors93/coalestra/issues
10
+ Keywords: snapshot,cache,batch,single-flight,integration,deadline,observability,orchestration,asyncio
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.10
15
+ Classifier: Programming Language :: Python :: 3.11
16
+ Classifier: Programming Language :: Python :: 3.12
17
+ Classifier: Programming Language :: Python :: 3.13
18
+ Classifier: Typing :: Typed
19
+ Requires-Python: >=3.10
20
+ Description-Content-Type: text/markdown
21
+ License-File: LICENSE
22
+ Provides-Extra: dev
23
+ Requires-Dist: build>=1.2; extra == "dev"
24
+ Requires-Dist: mypy>=1.10; extra == "dev"
25
+ Requires-Dist: pytest>=8.0; extra == "dev"
26
+ Requires-Dist: ruff>=0.6; extra == "dev"
27
+ Dynamic: license-file
28
+
29
+ # Coalestra
30
+
31
+ **Coalestra** is a dependency-free Python library for building consistent operational snapshots from prioritized read-only sources.
32
+
33
+ It coalesces duplicate requests, batches compatible resources, derives values from existing resources, bounds read concurrency, isolates failing source partitions, and accepts event-driven updates directly into its cache. The core is domain-agnostic and contains no knowledge of HTTP, SQL, Redis, Binance, trading, or Alphora.
34
+
35
+ ## Capabilities
36
+
37
+ - Immutable snapshots with provenance and freshness metadata.
38
+ - Single-stage builds and incremental multi-stage `SnapshotSession` workflows.
39
+ - Per-key single-flight coalescing across overlapping requests.
40
+ - Single-resource, batch, and derived source contracts in one priority chain.
41
+ - Recursive dependencies, dependency sharing, and cycle detection.
42
+ - Builder-wide concurrency limits shared by all builds and sessions.
43
+ - Optional per-source concurrency limits.
44
+ - Source-specific retry and circuit-breaker policies.
45
+ - Circuit isolation by source, namespace, subject, or full resource.
46
+ - Case-preserving resource identity with configurable normalization and qualifiers.
47
+ - Batch cache reads/writes, bounded LRU storage, pruning, invalidation, and cache statistics.
48
+ - Per-resource TTL, stale windows, stale-on-error fallback, and background refresh modes.
49
+ - Direct asynchronous and synchronous event publication into the cache.
50
+ - Monotonic publication that rejects older or duplicate events by default.
51
+ - Consolidated immutable diagnostics on every snapshot.
52
+ - Buffered event and metrics sinks that keep downstream I/O outside the acquisition path.
53
+ - Replaceable cache, clock, event, and metrics interfaces.
54
+ - Async API plus persistent synchronous facades.
55
+ - Required/optional resource requests for integration-safe partial snapshots.
56
+ - Deadline-aware retries, bounded batch chunking, and future-timestamp validation.
57
+ - Health snapshots for cache, circuits, capacity, refreshes, and in-flight work.
58
+ - Fast inline execution for explicitly non-blocking local synchronous sources.
59
+ - Strict static typing and no runtime dependencies.
60
+
61
+ ## Installation
62
+
63
+ ```bash
64
+ python -m pip install -e ".[dev]"
65
+ ```
66
+
67
+ Python 3.10 or newer is supported.
68
+
69
+ ## Integration-ready requests
70
+
71
+ Use `SnapshotRequest` to separate resources that must exist from resources that may fail without aborting the unit of work:
72
+
73
+ ```python
74
+ from coalestra import SnapshotRequest
75
+
76
+ request = SnapshotRequest(
77
+ required=[ACCOUNT, ALL_POSITIONS],
78
+ optional=[MARKET_HEALTH, LEARNING_CONTEXT],
79
+ )
80
+
81
+ snapshot = await builder.build_request(request, deadline_seconds=3.0)
82
+ ```
83
+
84
+ Only required failures raise `SnapshotBuildError`. The exception exposes a partial `snapshot`, so already resolved values and diagnostics are not lost. Sessions and synchronous facades expose the same request API.
85
+
86
+ ## Fast local sources and bounded batches
87
+
88
+ Synchronous adapters run in worker threads by default. Lock-protected, non-blocking in-memory reads can opt into inline execution:
89
+
90
+ ```python
91
+ local_source = CallableBatchSource(
92
+ name="market-state",
93
+ priority=100,
94
+ supports=supports_market,
95
+ fetcher=read_local_market_state,
96
+ run_sync_in_thread=False,
97
+ max_batch_size=100,
98
+ )
99
+ ```
100
+
101
+ Do not use inline execution for network, filesystem, database, or any potentially blocking operation. Large batches are split into capacity-aware waves, avoiding unbounded task creation.
102
+
103
+ ## Timestamp precision
104
+
105
+ `ObservationPolicy` rejects observations too far in the future, preventing clock errors from making values artificially fresh. Small accepted clock differences are recorded as `clock_skew_seconds` metadata.
106
+
107
+ ## Generic resource identity
108
+
109
+ `ResourceKey` preserves case by default, trims surrounding whitespace and supports immutable qualifiers:
110
+
111
+ ```python
112
+ from coalestra import ResourceKey
113
+
114
+ candles = ResourceKey(
115
+ "market",
116
+ "candles",
117
+ "BTCUSDT",
118
+ {"interval": "1m", "limit": 500},
119
+ )
120
+
121
+ assert candles.qualifier("interval") == "1m"
122
+ ```
123
+
124
+ Qualifiers are normalized into a sorted tuple, so mapping insertion order does not affect equality or hashing. Systems with case-insensitive identity can opt in to a normalizer:
125
+
126
+ ```python
127
+ from coalestra import CASE_INSENSITIVE_KEY_NORMALIZER, ResourceKey
128
+
129
+ key = ResourceKey(
130
+ "Tenant-A",
131
+ "DocumentId",
132
+ "/Path/File",
133
+ normalizer=CASE_INSENSITIVE_KEY_NORMALIZER,
134
+ )
135
+ ```
136
+
137
+ `LEGACY_KEY_NORMALIZER` and `ResourceKey.legacy(...)` reproduce Coalestra 0.1-0.3 behavior.
138
+
139
+ ## Batch cache operations and refresh policies
140
+
141
+ `AsyncMemoryCache` performs multi-key reads and writes under one lock, defaults to a bounded 10,000-entry LRU, removes fully expired entries on access, and exposes statistics and namespace invalidation. Custom caches may implement `BatchAsyncCache`; older single-key caches remain supported.
142
+
143
+ Freshness policies support three refresh modes:
144
+
145
+ ```python
146
+ from coalestra import FreshnessPolicy, RefreshMode
147
+
148
+ policy = FreshnessPolicy(
149
+ ttl_seconds=5.0,
150
+ max_stale_seconds=30.0,
151
+ refresh_mode=RefreshMode.STALE_WHILE_REVALIDATE,
152
+ )
153
+ ```
154
+
155
+ - `BLOCKING`: wait for a fresh source when the cache is outside TTL.
156
+ - `STALE_WHILE_REVALIDATE`: return an acceptable stale value and refresh it in the background.
157
+ - `REFRESH_AHEAD`: return a fresh value and refresh it before TTL expiry.
158
+
159
+ Long-lived asynchronous applications can call `await builder.wait_for_refreshes()`. `SyncSnapshotBuilder.close()` waits for pending refreshes before stopping its event loop.
160
+
161
+ ## Snapshot diagnostics
162
+
163
+ Every snapshot contains immutable acquisition diagnostics:
164
+
165
+ ```python
166
+ snapshot = await builder.build(keys)
167
+ print(snapshot.diagnostics.cache_hits)
168
+ print(snapshot.diagnostics.source_calls_by_source)
169
+ print(snapshot.diagnostics.observation_skew_ms)
170
+ ```
171
+
172
+ Diagnostics include requested, resolved and failed resource counts; cache hits/misses and batch operations; stale values and coalesced requests; source, batch and derived calls; refresh outcomes; per-source latency totals; total duration; and observation-time skew.
173
+
174
+ ## Buffered observability
175
+
176
+ Wrap a potentially slow sink so logging or metrics export does not run on the acquisition path:
177
+
178
+ ```python
179
+ from coalestra import BufferedEventSink, BufferedMetricsSink
180
+
181
+ events = BufferedEventSink(file_event_sink, max_pending=10_000)
182
+ metrics = BufferedMetricsSink(prometheus_adapter, max_pending=10_000)
183
+
184
+ builder = SnapshotBuilder(sources, events=events, metrics=metrics)
185
+
186
+ # During shutdown
187
+ events.close()
188
+ metrics.close()
189
+ ```
190
+
191
+ The default overflow policy drops the oldest queued record. `DROP_NEWEST` and `RAISE` are also available. Delivery failures are counted and never injected into resource resolution.
192
+
193
+ ## Minimal build
194
+
195
+ ```python
196
+ import asyncio
197
+
198
+ from coalestra import CallableSource, ResourceKey, SnapshotBuilder
199
+
200
+ PRICE = ResourceKey("market", "price", "BTCUSDT")
201
+
202
+ builder = SnapshotBuilder(
203
+ [
204
+ CallableSource(
205
+ name="rest",
206
+ priority=10,
207
+ supports=lambda key: key == PRICE,
208
+ fetcher=lambda _key, _context: {"price": "65000.00"},
209
+ )
210
+ ]
211
+ )
212
+
213
+ snapshot = asyncio.run(builder.build([PRICE]))
214
+ print(snapshot.value(PRICE, dict))
215
+ ```
216
+
217
+ ## Batch sources
218
+
219
+ A batch source receives every unresolved compatible key available at its priority level. It may return a partial mapping; omitted resources continue through lower-priority sources.
220
+
221
+ ```python
222
+ from coalestra import CallableBatchSource
223
+
224
+ async def fetch_prices(keys, _context):
225
+ symbols = [key.subject for key in keys]
226
+ response = await remote_api.fetch_prices(symbols)
227
+ return {key: response[key.subject] for key in keys if key.subject in response}
228
+
229
+ price_source = CallableBatchSource(
230
+ name="price-api",
231
+ priority=100,
232
+ supports=lambda key: key.namespace == "market" and key.name == "price",
233
+ fetcher=fetch_prices,
234
+ )
235
+ ```
236
+
237
+ ## Incremental sessions
238
+
239
+ A session keeps one identity, creation time, deadline, and pinned-value memo across multiple stages. Read capacity is controlled by the long-lived builder and is therefore shared with every other active build and session.
240
+
241
+ ```python
242
+ async with builder.session(
243
+ snapshot_id="cycle-42",
244
+ deadline_seconds=3.0,
245
+ metadata={"tenant": "example"},
246
+ ) as session:
247
+ baseline = await session.resolve(baseline_keys, strict=False)
248
+ selected = choose_resources_from(baseline)
249
+ final = await session.resolve(selected, strict=False)
250
+ ```
251
+
252
+ Successful values remain pinned inside the session. Existing errors can be retried explicitly:
253
+
254
+ ```python
255
+ await session.resolve([KEY], retry_errors=True)
256
+ ```
257
+
258
+ ## Derived resources
259
+
260
+ Derived sources declare dependencies and calculate a resource from an immutable dependency snapshot.
261
+
262
+ ```python
263
+ from coalestra import CallableDerivedSource, ResourceKey
264
+
265
+ EXCHANGE_INFO = ResourceKey("exchange", "info")
266
+
267
+ rules_source = CallableDerivedSource(
268
+ name="symbol-rules",
269
+ priority=100,
270
+ supports=lambda key: key.namespace == "exchange" and key.name == "rules",
271
+ dependencies=lambda _key: (EXCHANGE_INFO,),
272
+ deriver=lambda key, snapshot, _context: extract_rules(
273
+ snapshot.value(EXCHANGE_INFO, dict),
274
+ key.subject,
275
+ ),
276
+ )
277
+ ```
278
+
279
+ Dependencies may themselves be cached, batched, fetched, or derived. Direct and indirect cycles are rejected.
280
+
281
+ ## Global and per-source capacity
282
+
283
+ `max_concurrency` is a builder-wide limit. Concurrent calls to `build()` and multiple active sessions share the same capacity.
284
+
285
+ ```python
286
+ builder = SnapshotBuilder(
287
+ sources,
288
+ max_concurrency=12,
289
+ source_concurrency={
290
+ "remote-rest": 4,
291
+ "database": 6,
292
+ },
293
+ )
294
+ ```
295
+
296
+ Callable adapters can also declare their own limit:
297
+
298
+ ```python
299
+ rest_source = CallableSource(
300
+ name="remote-rest",
301
+ priority=10,
302
+ supports=supports_rest,
303
+ fetcher=fetch_rest,
304
+ max_concurrency=4,
305
+ )
306
+ ```
307
+
308
+ An explicit `source_concurrency` entry overrides the limit declared by the source. Batch calls consume one slot regardless of batch size. Derivation consumes a slot only while the derivation function itself runs; dependency acquisition uses its own source slots.
309
+
310
+ ## Source-specific resilience and circuit scopes
311
+
312
+ ```python
313
+ from coalestra import (
314
+ CircuitBreakerPolicy,
315
+ CircuitScope,
316
+ RetryPolicy,
317
+ SourceResiliencePolicy,
318
+ )
319
+
320
+ stream_policy = SourceResiliencePolicy(
321
+ retry=RetryPolicy(max_attempts=1),
322
+ circuit=CircuitBreakerPolicy(
323
+ scope=CircuitScope.SUBJECT,
324
+ failure_threshold=2,
325
+ recovery_timeout_seconds=5.0,
326
+ ),
327
+ )
328
+
329
+ stream_source = CallableSource(
330
+ name="market-stream",
331
+ priority=100,
332
+ supports=supports_market,
333
+ fetcher=read_stream_state,
334
+ resilience_policy=stream_policy,
335
+ )
336
+ ```
337
+
338
+ Available scopes:
339
+
340
+ - `SOURCE`: one circuit for the complete source;
341
+ - `NAMESPACE`: one circuit per source and resource namespace;
342
+ - `SUBJECT`: one circuit per source and subject;
343
+ - `RESOURCE`: one circuit per complete `ResourceKey`.
344
+
345
+ A stale payload is treated as an unsuccessful circuit outcome for its configured scope. With `SUBJECT`, stale data for one symbol does not disable the source for other symbols.
346
+
347
+ Policies can also be supplied centrally through `source_resilience` or a `ResiliencePolicyResolver`.
348
+
349
+ ## Direct event publication
350
+
351
+ A long-lived builder exposes a `ResourcePublisher` backed by the same cache used by snapshot acquisition.
352
+
353
+ ```python
354
+ await builder.publisher.publish(
355
+ PRICE,
356
+ {"price": "65001.25"},
357
+ source="market-stream",
358
+ observed_at=event_timestamp,
359
+ metadata={"sequence": sequence},
360
+ )
361
+ ```
362
+
363
+ Publication is monotonic by default:
364
+
365
+ - an older `observed_at` is ignored;
366
+ - an equal timestamp is treated as a duplicate;
367
+ - `force=True` permits explicit reconciliation or repair;
368
+ - `replace_equal=True` permits replacement at the same timestamp.
369
+
370
+ Several updates can be published together:
371
+
372
+ ```python
373
+ from coalestra import ResourceUpdate
374
+
375
+ await builder.publisher.publish_many(
376
+ [
377
+ ResourceUpdate(PRICE_BTC, btc, source="stream", observed_at=btc_time),
378
+ ResourceUpdate(PRICE_ETH, eth, source="stream", observed_at=eth_time),
379
+ ]
380
+ )
381
+ ```
382
+
383
+ Uncertain state can be invalidated:
384
+
385
+ ```python
386
+ await builder.publisher.invalidate(POSITION_BTC, reason="stream-gap")
387
+ ```
388
+
389
+ A session intentionally keeps values already pinned before a publication. New builds and new sessions observe the published value.
390
+
391
+ ## Synchronous applications
392
+
393
+ `SyncSnapshotBuilder` owns one persistent event-loop thread. Keep it alive for the application lifetime.
394
+
395
+ ```python
396
+ from coalestra import SyncSnapshotBuilder
397
+
398
+ with SyncSnapshotBuilder(builder) as sync_builder:
399
+ sync_builder.publisher.publish(
400
+ PRICE,
401
+ {"price": "65001.25"},
402
+ source="market-stream",
403
+ )
404
+
405
+ # Suitable for callbacks that should not block their producer thread.
406
+ future = sync_builder.publisher.submit_publish(
407
+ POSITION,
408
+ position,
409
+ source="user-stream",
410
+ observed_at=event_timestamp,
411
+ )
412
+
413
+ snapshot = sync_builder.build([PRICE, POSITION])
414
+ future.result()
415
+ ```
416
+
417
+ Synchronous fetchers and derivation functions run in worker threads by default. `run_sync_in_thread=False` is available only for guaranteed non-blocking local reads. Transport-level timeouts remain necessary because an already-running Python thread cannot be forcibly terminated. Closing the synchronous facade closes its underlying builder by default.
418
+
419
+ ## Architectural boundary
420
+
421
+ Coalestra owns read acquisition, cache publication, freshness, coalescing, fallback, derivation, and read concurrency. The consuming application owns business decisions, authorization, risk, writes, transactions, and domain validation.
422
+
423
+ ## Project layout
424
+
425
+ ```text
426
+ src/coalestra/
427
+ ├── adapters/ # Callable single, batch, and derived sources
428
+ ├── cache/ # Cache implementations and event publisher
429
+ ├── concurrency/ # Builder-wide and per-source capacity control
430
+ ├── core/ # Models, protocols, and errors
431
+ ├── observability/ # Event and metrics sinks
432
+ ├── orchestration/ # Builder, session, policy, and single-flight
433
+ ├── resilience/ # Retry, circuit policies, and circuit breaker
434
+ └── sync.py # Persistent synchronous facades
435
+ ```
436
+
437
+ ## Quality pipeline
438
+
439
+ ```bash
440
+ make quality
441
+ ```
442
+
443
+ This runs formatting, linting, strict mypy, verifies the 500-test minimum, executes the full 618-test suite, and builds the package.
444
+
445
+ ## Documentation
446
+
447
+ - [Architecture](docs/architecture.md)
448
+ - [Public API](docs/public-api.md)
449
+ - [Migration from 0.4](docs/migration-0.5.md)
450
+ - [Revisão final para integração](docs/integration-readiness.pt-BR.md)
451
+ - [Integração com o Alphora](docs/alphora-integration.pt-BR.md)
452
+ - [Changelog](CHANGELOG.md)
453
+
454
+ ## License
455
+
456
+ MIT